C Lib
C Lib
Arithmetic Functions
There are 4 basic integer functions:
int abs(int number);
long int labs(long int number);
Essentially there are two functions with integer and long integer compatibility.
abs
functions return the absolute value of its number arguments. For example, abs(2) returns
2 as does abs(-2).
div
takes two arguments, numerator and denominator and produces a quotient and a
remainder of the integer division. The div_t structure is defined (in stdlib.h) as
follows:
typedef struct {
int quot; /* quotient */
int rem; /* remainder */
} div_t;
Thus:
#include <stdlib.h>
....
1
int num = 8, den = 3;
div_t ans;
ans = div(num,den);
Answer:
Quotient = 2
Remainder = 2
Random Numbers
Random numbers are useful in programs that need to simulate random events, such as games,
simulations and experimentations. In practice no functions produce truly random data -- they
produce pseudo-random numbers. These are computed form a given formula (different
generators use different formulae) and the number sequences they produce are repeatable. A
seed is usually set from which the sequence is generated. Therefore is you set the same seed all
the time the same set will be be computed.
One common technique to introduce further randomness into a random number generator is to
use the time of the day to set the seed, as this will always be changing. (We will study the
standard library time functions later in Chapter 20).
There are many (pseudo) random number functions in the standard library. They all operate on
the same basic idea but generate different number sequences (based on different generator
functions) over different number ranges.
The simplest set of functions is:
int rand(void);
void srand(unsigned int seed);
The following program card.c illustrates the use of these functions to simulate a pack of cards
being shuffled:
/*
** Use random numbers to shuffle the "cards" in the deck. The second
** argument indicates the number of cards. The first time this
** function is called, srand is called to initialize the random
** number generator.
*/
#include <stdlib.h>
2
#include <time.h>
#define TRUE 1
#define FALSE 0
/*
** Seed the random number generator with the current time
** of day if we haven't done so yet.
*/
if( first_time ){
first_time = FALSE;
srand( (unsigned int)time( NULL ) );
}
/*
** "Shuffle" by interchanging random pairs of cards.
*/
for( i = n_cards - 1; i > 0; i -= 1 ){
int where;
int temp;
where = rand() % i;
temp = deck[ where ];
deck[ where ] = deck[ i ];
deck[ i ] = temp;
}
}
There are several other random number generators available in the standard library:
double drand48(void);
double erand48(unsigned short xsubi[3]);
long lrand48(void);
long nrand48(unsigned short xsubi[3]);
long mrand48(void);
long jrand48(unsigned short xsubi[3]);
void srand48(long seed);
unsigned short *seed48(unsigned short seed[3]);
void lcong48(unsigned short param[7]);
3
Further examples of using these functions is given is Chapter 20.
String Conversion
There are a few functions that exist to convert strings to integer, long integer and float values.
They are:
double atof(char *string) -- Convert string to floating point value.
int atoi(char *string) -- Convert string to an integer value
int atol(char *string) -- Convert string to a long integer value.
double strtod(char *string, char *endptr) -- Convert string to a floating
point value.
long strtol(char *string, char *endptr, int radix) -- Convert string to a
long integer using a given radix.
unsigned long strtoul(char *string, char *endptr, int radix) --
Convert string to unsigned long.
Most of these are fairly straightforward to use. For example:
char *str1 = "100";
char *str2 = "55.444";
char *str3 = " 1234";
char *str4 = "123four";
char *str5 = "invalid123";
int i;
float f;
i = atoi(str1); /* i = 100 */
f = atof(str2); /* f = 55.44 */
i = atoi(str3); /* i = 1234 */
i = atoi(str4); /* i = 123 */
i = atoi(str5); /* i = 0 */
Note:
• Leading blank characters are skipped.
• Trailing illegal characters are ignored.
• If conversion cannot be made zero is returned and errno (See Chapter 17) is set with
the value ERANGE.
4
qsort is prototyped (in stdlib.h):
void qsort(void *base, size_t num_elements, size_t element_size,
int (*compare)(void const *, void const *));
Similarly, there is a binary search function, bsearch() which is prototyped (in stdlib.h)
as:
void *bsearch(const void *key, const void *base, size_t nel,
size_t size, int (*compare)(const void *, const void *));
Using the same Record structure and record_compare function as the qsort() example
(in Chapter 11.3):
typedef struct {
int key;
struct
other_data;
} Record;
Also, Assuming that we have an array of array_length Records suitably filled with
date we can call bsearch() like this:
Record key;
Record *ans;
The function bsearch() return a pointer to the field whose key filed is filled with the matched
value of NULL if no match found.
Note that the type of the key argument must be the same as the array elements (Record
above), even though only the key.key element is required to be set.
Exercises
Exercise 12534
Write a program that simulates throwing a six sided die
Exercise 12535
Write a program that simulates the UK National lottery by selecting six different whole numbers
in the range 1 - 49.
Exercise 12536
Write a program that read a number from command line input and generates a random floating
point number in the range 0 - the input number.
5
Input and Output (I/O):stdio.h
This chapter will look at many forms of I/O. We have briefly mentioned some forms before will
look at these in much more detail here.
Your programs will need to include the standard I/O header file so do:
#include <stdio.h>
Reporting Errors
Many times it is useful to report errors in a C program. The standard library perror() is an
easy to use and convenient function. It is used in conjunction with errno and frequently on
encountering an error you may wish to terminate your program early. Whilst not strictly part of
the stdio.h library we introduce the concept of errno and the function exit() here. We
will meet these concepts in other parts of the Standard Library also.
perror()
The function perror() is prototyped by:
void perror(const char *message);
perror() produces a message (on standard error output -- see Section 17.2.1), describing the last
error encountered, returned to errno (see below) during a call to a system or library function.
The argument string message is printed first, then a colon and a blank, then the message and a
newline. If message is a NULL pointer or points to a null string, the colon is not printed.
errno
errno is a special system variable that is set if a system call cannot perform its set task. It is
defined in #include <errno.h>.
To use errno in a C program it must be declared via:
extern int errno;
It can be manually reset within a C program (although this is uncommon practice) otherwise it
simply retains its last value returned by a system call or library function.
exit()
The function exit() is prototyped in #include <stdlib> by:
void exit(int status)
Exit simply terminates the execution of a program and returns the exit status value to the
operating system. The status value is used to indicate if the program has terminated properly:
6
• it exist with a EXIT_SUCCESS value on successful termination
• it exist with a EXIT_FAILURE value on unsuccessful termination.
On encountering an error you may frequently call an exit(EXIT_FAILURE) to terminate an
errant program.
Streams
Streams are a portable way of reading and writing data. They provide a flexible and efficient
means of I/O.
A Stream is a file or a physical device (e.g. printer or monitor) which is manipulated with a
pointer to the stream.
There exists an internal C data structure, FILE, which represents all streams and is defined in
stdio.h. We simply need to refer to the FILE structure in C programs when performing I/O
with streams.
We just need to declare a variable or pointer of this type in our programs.
We do not need to know any more specifics about this definition.
We must open a stream before doing any I/O,
then access it
and then close it.
Stream I/O is BUFFERED: That is to say a fixed ``chunk'' is read from or written to a file via
some temporary storage area (the buffer). This is illustrated in Fig. 17.1. NOTE the file pointer
actually points to this buffer.
Fig. Stream I/O Model This leads to efficient I/O but beware: data written to a buffer does not
appear in a file (or device) until the buffer is flushed or written out. ( n does this). Any
abnormal exit of code can cause problems.
7
Predefined Streams
UNIX defines 3 predefined streams (in stdio.h):
stdin, stdout, stderr
They all use text a the method of I/O.
stdin and stdout can be used with files, programs, I/O devices such as keyboard, console,
etc.. stderr always goes to the console or screen.
The console is the default for stdout and stderr. The keyboard is the default for stdin.
Predefined stream are automatically open.
Redirection
This how we override the UNIX default predefined I/O defaults.
This is not part of C but operating system dependent. We will do redirection from the command
line.
> -- redirect stdout to a file.
So if we have a program, out, that usually prints to the screen then
out > file1
will send the output to a file, file1.
< -- redirect stdin from a file to a program.
So if we are expecting input from the keyboard for a program, in we can read similar input
from a file
in < file2.
| -- pipe: puts stdout from one program to stdin of another
prog1 | prog2
e.g. Sent output (usually to console) of a program direct to printer:
out | lpr
Basic I/O
There are a couple of function that provide basic I/O facilities.
probably the most common are: getchar() and putchar(). They are defined and used as
follows:
• int getchar(void) -- reads a char from stdin
• int putchar(char ch) -- writes a char to stdout, returns character written.
int ch;
8
ch = getchar();
(void) putchar((char) ch);
Related Functions:
int getc(FILE *stream),
int putc(char ch,FILE *stream)
Formatted I/O
We have seen examples of how C uses formatted I/O already. Let's look at this in more detail.
Printf
The function is defined as follows:
int printf(char *format, arg list ...) --
prints to stdout the list of arguments according specified format string. Returns number of
characters printed.
The format string has 2 types of object:
• ordinary characters -- these are copied to output.
• conversion specifications -- denoted by % and listed in Table 17.1.
terminated by 0
f double/float format -m.ddd...
e,E " Scientific Format
-1.23e002
9
g,G " e or f whichever
is most compact
% - print % character
So:
printf("%-2.3f n",17.23478);
17.235
and:
printf("VAT=17.5%% n");
...outputs:
VAT=17.5%
scanf
This function is defined as follows:
int scanf(char *format, args....) -- reads from stdin and puts input in
address of variables specified in args list. Returns number of chars read.
Format control string similar to printf
10
Note: The ADDRESS of variable or a pointer to one is required by scanf.
scanf(``%d'',&i);
We can just give the name of an array or string to scanf since this corresponds to the start
address of the array/string.
char string[80];
scanf(``%s'',string);
Files
Files are the most common form of a stream.
The first thing we must do is open a file. The function fopen() does this:
FILE *fopen(char *name, char *mode)
fopen returns a pointer to a FILE. The name string is the name of the file on disc that we wish
to access. The mode string controls our type of access. If a file cannot be accessed for any
reason a NULL pointer is returned.
To open a file we must have a stream (file pointer) that points to a FILE structure.
So to open a file, called myfile.dat for reading we would do:
stream = fopen(``myfile.dat'',``r'');
11
Reading and writing files
The functions fprintf and fscanf a commonly used to access files.
These are similar to printf and scanf except that data is read from the
stream that must have been opened with fopen().
The stream pointer is automatically incremented with ALL file
read/write functions. We do not have to worry about doing this.
char *string[80]
FILE *stream, *fopen();
12
float miles = 300;
char miles_per_litre[80];
while ( !feof(fp) )
fscanf(fp,"%s",line);
ferror()
-- reports on the error state of the stream and returns true if an error has occurred.
clearerr()
-- resets the error indication for a given stream.
fileno()
-- returns the integer file descriptor associated with the named stream.
13
O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_WRONLY + others see
online man pages or reference manuals.
perms -- best set to 0 for most of our applications.
The function:
creat(char *filename, int perms)
can also be used to create a file.
int close(int handle) -- close a file
int read(int handle, char *buffer,
unsigned length)
int write(int handle, char *buffer, unsigned length)
are used to read/write a specific number of bytes from/to a file (handle) stored or to be put in the
memory location specified by buffer.
The sizeof() function is commonly used to specify the length.
read and write return the number of bytes read/written or -1 if they fail.
#include<stdio.h>
#include<fcntl.h>
float bigbuff[1000];
{ int fd;
int bytes_read;
int file_length;
14
Exercises
Exercise 12573
Write a program to copy one named file into another named file. The two file names are given as
the first two arguments to the program.
Copy the file a block (512 bytes) at a time.
Check: that the program has two arguments
or print "Program need two arguments"
that the first name file is readable
or print "Cannot open file .... for reading"
that the second file is writable
or print "Cannot open file .... for writing"
Exercise 12577
Write a program last that prints the last n lines of a text file, by n and the file name should be
specified form command line input. By default n should be 5, but your program should allow an
optional argument so that
last -n file.txt
prints out the last n lines, where n is any integer. Your program should make the best use of
available storage.
Exercise 12578
Write a program to compare two files and print out the lines where they differ. Hint: look up
appropriate string and file handling library routines. This should not be a very long program.
15
String Handling: <string.h>
Recall from our discussion of arrays (Chapter 6) that strings are defined as an array of
characters or a pointer to a portion of memory containing ASCII characters. A string in C is a
It is important to preserve the NULL terminating character as it is how C defines and manages
variable length strings. All the C standard library functions require this for successful operation.
In general, apart from some length-restricted functions ( strncat(), strncmp,() and
strncpy()), unless you create strings by hand you should not encounter any such problems, .
You should use the many useful string handling functions and not really need to get your hands
dirty dismantling and assembling strings.
16
char *str2;
int length;
Note that both strcat() and strcopy() both return a copy of their first argument which is
the destination array. Note the order of the arguments is destination array followed by source
array which is sometimes easy to get the wrong around when programming.
The strcmp() function lexically compares the two input strings and returns:
Less than zero
-- if string1 is lexically less than string2
Zero
-- if string1 and string2 are lexically equal
Greater than zero
-- if string1 is lexically greater than string2
This can also confuse beginners and experience programmers forget this too.
The strncat(), strncmp,() and strncpy() copy functions are string restricted
version of their more general counterparts. They perform a similar task but only up to the first n
characters. Note the the NULL terminated requirement may get violated when using these
functions, for example:
char *str1 = "HELLO";
char *str2;
int length = 2;
String Searching
The library also provides several string searching functions:
char *strchr(const char *string, int c) -- Find first occurrence of character c
in string.
char *strrchr(const char *string, int c) -- Find last occurrence of character
c in string.
char *strstr(const char *s1, const char *s2) -- locates the first occurrence
of the string s2 in string s1.
char *strpbrk(const char *s1, const char *s2) -- returns a pointer to the
first occurrence in string s1 of any character from string s2, or a null pointer if no character
from s2 exists in s1
size_t strspn(const char *s1, const char *s2) -- returns the number of
characters at the begining of s1 that match s2.
size_t strcspn(const char *s1, const char *s2) -- returns the number of
17
characters at the begining of s1 that do not match s2.
char *strtok(char *s1, const char *s2) -- break the string pointed to by s1
into a sequence of tokens, each of which is delimited by one or more characters from the string
pointed to by s2.
char *strtok_r(char *s1, const char *s2, char **lasts) -- has the same
functionality as strtok() except that a pointer to a string placeholder lasts must be supplied by the
caller.
strchr() and strrchr() are the simplest to use, for example:
char *str1 = "Hello";
char *ans;
ans = strchr(str1,'l');
ans = strpbrk(str1,'aeiou');
Here, ans points to the location str1 + 1, the location of the first e.
strstr() returns a pointer to the specified search string or a null pointer if the string is not found.
If s2 points to a string with zero length (that is, the string ""), the function returns s1. For
example,
char *str1 = "Hello";
char *ans;
ans = strstr(str1,'lo');
printf("%s\n",t1);
18
• The initialisation calls strtok() loads the function with the string str1
• We terminate when t1 is NULL
• We keep assigning tokens of str1 to t1 until termination by calling strtok() with a
NULL first argument.
19
Their use is fairly straightforward and not dissimilar to comparable string operations (except the
exact length (n) of the operations must be specified as there is no natural termination here).
Note that in all case to bytes of memory are copied. The sizeof() function comes in handy
again here, for example:
char src[SIZE],dest[SIZE];
int isrc[SIZE],idest[SIZE];
memmove() behaves in exactly the same way as memcpy() except that the source and
destination locations may overlap.
memcmp() is similar to strcmp() except here unsigned bytes are compared and returns less
than zero if s1 is less than s2 etc.
Exercises
Exercise 12584
Write a function similar to strlen that can handle unterminated strings. Hint: you will need to
know and pass in the length of the string.
Exercise 12585
Write a function that returns true if an input string is a palindrome of each other. A palindrome is
a word that reads the same backwards as it does forwards e.g ABBA.
Exercise 12586
Suggest a possible implementation of the strtok() function:
1.
using other string handling functions.
2.
from first pointer principles
20
File Access and Directory System Calls
There are many UNIX utilities that allow us to manipulate directories and files. cd, ls,
rm, cp, mkdir etc. are examples we have (hopefully) already met.
We will now see how to achieve similar tasks from within a C program.
#include<stdio.h>
#include<unistd.h>
n'',argv[0]);
exit(1);
}
if (chdir(argv[1]) != 0)
{ printf(``Error in chdir
n'');
exit(1);
}
}
21
int (*compar)()) -- reads the directory dirname and builds an array of pointers to
directory entries or -1 for an error. namelist is a pointer to an array of structure pointers.
(*select))() is a pointer to a function which is called with a pointer to a directory entry
(defined in <sys/types> and should return a non zero value if the directory entry should be
included in the array. If this pointer is NULL, then all the directory entries will be included.
The last argument is a pointer to a routine which is passed to qsort (see man qsort) -- a
built in function which sorts the completed array. If this pointer is NULL, the array is not sorted.
alphasort(struct direct **d1, **d2) -- alphasort() is a built in routine which will
sort the array alphabetically.
Example - a simple C version of UNIX ls utility
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/param.h>
#include <stdio.h>
#define FALSE 0
#define TRUE !FALSE
char pathname[MAXPATHLEN];
if (getwd(pathname) == NULL )
22
printf(``%s '',files[i-1]-
>d_name);
{char *ptr;
char *rindex(char *s, char c);
if ((strcmp(entry->d_name, ``.'')== 0) ||
(strcmp(entry->d_name,
``..'') == 0))
return (FALSE);
(strcmp(ptr, ``.h'') == 0)
(strcmp(ptr, ``.o'') == 0)
23
))
return
(TRUE);
else
return(FALSE);
}
File Access
int access(char *path, int mode) -- determine accessibility of file.
path points to a path name naming a file. access() checks the named file for accessibility
according to mode, defined in #include <unistd.h>:
R_OK
- test for read permission
W_OK
- test for write permission
X_OK
- test for execute or search permission
F_OK
- test whether the directories leading to the file can be searched and the file exists.
access() returns: 0 on success, -1 on failure and sets errno to indicate the error. See man
pages for list of errors.
errno
errno is a special system variable that is set if a system call cannot perform its set task.
To use errno in a C program it must be declared via:
extern int errno;
It can be manually reset within a C program other wise it simply retains its last value.
int chmod(char *path, int mode) change the mode of access of a file. specified by
24
path to the given mode.
chmod() returns 0 on success, -1 on failure and sets errno to indicate the error. Errors are
defined in #include <sys/stat.h>
The access mode of a file can be set using predefined macros in sys/stat.h -- see man pages
-- or by setting the mode in a a 3 digit octal number.
The rightmost digit specifies owner privileges, middle group privileges and the leftmost other
users privileges.
For each octal digit think of it a 3 bit binary number. Leftmost bit = read access (on/off) middle
is write, right is executable.
So 4 (octal 100) = read only, 2 (010) = write, 6 (110) = read and write, 1 (001) = execute.
so for access mode 600 gives user read and write access others no access. 666 gives everybody
read/write access.
NOTE: a UNIX command chmod also exists
File Status
Two useful functions exist to inquire about the files current status. You can find out how large
the file is (st_size) when it was created (st_ctime) etc. (see stat structure definition
below. The two functions are prototyped in <sys/stat.h>
int stat(char *path, struct stat *buf),
int fstat(int fd, struct
stat *buf)
stat() obtains information about the file named by path. Read, write or execute permission of
the named file is not required, but all directories listed in the path name leading to the file must
be searchable.
fstat() obtains the same information about an open file referenced by the argument
descriptor, such as would be obtained by an open call (Low level I/O).
stat(), and fstat() return 0 on success, -1 on failure and sets errno to indicate the
error. Errors are again defined in #include <sys/stat.h>
buf is a pointer to a stat structure into which information is placed concerning the file. A stat
structure is define in #include <sys/types.h>, as follows
struct stat {
mode_t st_mode; /* File mode (type, perms) */
ino_t st_ino; /* Inode number */
dev_t st_dev; /* ID of device containing */
/* a directory entry for this file */
dev_t st_rdev; /* ID of device */
/* This entry is defined only for */
/* char special or block special files */
nlink_t st_nlink; /* Number of links */
uid_t st_uid; /* User ID of the file's owner */
25
gid_t st_gid; /* Group ID of the file's group */
off_t st_size; /* File size in bytes */
time_t st_atime; /* Time of last access */
time_t st_mtime; /* Time of last data modification */
time_t st_ctime; /* Time of last file status change */
/* Times measured in seconds since */
/* 00:00:00 UTC, Jan. 1, 1970 */
long st_blksize; /* Preferred I/O block size */
blkcnt_t st_blocks; /* Number of 512 byte blocks allocated*/
}
Two system calls (defined in unistd.h) which are actually used by remove() and
rename() also exist but are probably harder to remember unless you are familiar with UNIX.
int unlink(cons char *path) -- removes the directory entry named by path
unlink() returns 0 on success, -1 on failure and sets errno to indicate the error. Errors listed
in #include <sys/stat.h>
A similar function link(const char *path1, const char *path2) creates a
linking from an existing directory entry path1 to a new entry path2
Exercises
Exercise 12675
Write a C program to emulate the ls -l UNIX command that prints all files in a current
directory and lists access privileges etc. DO NOT simply exec ls -l from the program.
26
Exercise 12676
Write a program to print the lines of a file which contain a word given as the program argument
(a simple version of grep UNIX utility).
Exercise 12677
Write a program to list the files given as arguments, stopping every 20 lines until a key is hit.(a
simple version of more UNIX utility)
Exercise 12678
Write a program that will list all files in a current directory and all files in subsequent sub
directories.
Exercise 12679
Write a program that will only list subdirectories in alphabetical order.
Exercise 12680
Write a program that shows the user all his/her C source programs and then prompts
interactively as to whether others should be granted read permission; if affirmative such
permission should be granted.
Exercise 12681
Write a program that gives the user the opportunity to remove any or all of the files in a current
working directory. The name of the file should appear followed by a prompt as to whether it
should be removed.
27
Time Functions
In this chapter we will look at how we can access the clock time with UNIX system calls.
There are many more time functions than we consider here - see man pages and standard library
function listings for full details. In this chapter we concentrate on applications of timing
functions in C
Uses of time functions include:
• telling the time.
• timing programs and functions.
• setting number seeds.
struct timeb
{ time_t time;
unsigned short millitm;
short timezone;
short dstflag;
};
The structure contains the time since the epoch in seconds, up to 1000 milliseconds of more
precise interval, the local time zone (measured in minutes of time westward from Greenwich),
and a flag that, if nonzero, indicates that Day light Saving time applies locally during the
appropriate part of the year.
On success, ftime() returns no useful value. On failure, it returns
-1.
Two other functions defined etc. in #include <time.h>
char *ctime(time_t *clock),
char *asctime(struct tm *tm)
28
ctime() converts a long integer, pointed to by clock, to a 26-
character string of the form produced by asctime(). It first
breaks down clock to a tm structure by calling localtime(), and
then calls asctime() to convert that tm structure to a string.
asctime() converts a time value contained in a tm structure to a
26-character string of the form:
Sun Sep 16 01:03:52 1973
asctime() returns a pointer to the string.
This is a simple program that illustrates that calling the time function at distinct moments and
noting the different times is a simple method of timing fragments of code:
/* timer.c */
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
main()
{ int i;
time_t t1,t2;
(void) time(&t1);
for (i=1;i<=300;++i)
29
/* random.c */
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
main()
{ int i;
time_t t1;
(void) time(&t1);
srand48((long) t1);
/* use time in seconds to set seed */
printf(``5 random numbers
Exercises
Exercise 12708
Write a C program that times a fragment of code in milliseconds.
Exercise 12709
Write a C program to produce a series of floating point random numbers in the ranges (a) 0.0 -
1.0 (b) 0.0 - n where n is any floating point value. The seed should be set so that a unique
sequence is guaranteed.
30
Process Control:
<stdlib.h>,<unistd.h>
A process is basically a single running program. It may be a ``system'' program (e.g login,
update, csh) or program initiated by the user (textedit, dbxtool or a user written one).
When UNIX runs a process it gives each process a unique number - a process ID, pid.
The UNIX command ps will list all current processes running on your machine and will list the
pid.
The C function int getpid() will return the pid of process that called this function.
A program usually runs as a single process. However later we will see how we can make
programs run as several separate communicating processes.
main()
execl()
execl has 5 other related functions -- see man pages.
execl stands for execute and leave which means that a process will get executed and then
terminated by execl.
It is defined by:
execl(char *path, char *arg0,...,char *argn, 0);
31
The last parameter must always be 0. It is a NULL terminator. Since the argument list is variable
we must have some way of telling C when it is to end. The NULL terminator does this job.
where path points to the name of a file holding a command that is to be executed, argo points
to a string that is the same as path (or at least its last component.
arg1 ... argn are pointers to arguments for the command and 0 simply marks the end of
the (variable) list of arguments.
So our above example could look like this also:
main()
fork()
int fork() turns a single process into 2 identical processes, known as the parent and the
child. On success, fork() returns 0 to the child process and returns the process ID of the child
process to the parent process. On failure, fork() returns -1 to the parent process, sets errno to
indicate the error, and no child process is created.
NOTE: The child process will have its own unique PID.
The following program illustrates a simple use of fork, where two copies are made and run
together (multitasking)
main()
{ int return_value;
Forking process
The process id is 6753 and return value is 0
32
The process id is 6754 and return value is 0
two lists of files in current directory
pid = fork();
if ( pid < 0 )
wait()
int wait (int *status_location) -- will force a parent process to wait for a child
process to stop or terminate. wait() return the pid of the child or -1 for an error. The exit status
of the child is returned to status_location.
exit()
void exit(int status) -- terminates the process which calls this function and returns
the exit status value. Both UNIX and C (forked) programs can read the status value.
By convention, a status of 0 means normal termination any other value indicates an error or
unusual occurrence. Many standard library calls have errors defined in the sys/stat.h header
file. We can easily derive our own conventions.
A complete example of forking program is originally titled fork.c:
/* fork.c - example of a fork in a program */
/* The program asks for UNIX commands to be typed and inputted to a string*/
/* The string is then "parsed" by locating blanks etc. */
/* Each command and sorresponding arguments are put in a args array */
33
/* execvp is called to execute these commands in child process */
/* spawned by fork() */
/* cc -o fork fork.c */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
main()
{
char buf[1024];
char *args[64];
for (;;) {
/*
* Prompt for and read a command.
*/
printf("Command: ");
if (gets(buf) == NULL) {
printf("\n");
exit(0);
}
/*
* Split the string into arguments.
*/
parse(buf, args);
/*
* Execute the command.
*/
execute(args);
}
}
/*
* parse--split the command in buf into
* individual arguments.
*/
parse(buf, args)
char *buf;
char **args;
{
while (*buf != NULL) {
/*
* Strip whitespace. Use nulls, so
* that the previous argument is terminated
* automatically.
*/
while ((*buf == ' ') || (*buf == '\t'))
*buf++ = NULL;
/*
* Save the argument.
*/
*args++ = buf;
34
/*
* Skip over the argument.
*/
while ((*buf != NULL) && (*buf != ' ') && (*buf != '\t'))
buf++;
}
*args = NULL;
}
/*
* execute--spawn a child process and execute
* the program.
*/
execute(args)
char **args;
{
int pid, status;
/*
* Get a child process.
*/
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
/*
* The child executes the code inside the if.
*/
if (pid == 0) {
execvp(*args, args);
perror(*args);
exit(1);
/* NOTE: The execv() vnd execvp versions of execl() are useful when
the
number of arguments is unknown in advance;
The arguments to execv() and execvp() are the name
of the file to be executed and a vector of strings contain-
ing the arguments. The last argument string must be fol-
lowed by a 0 pointer.
/*
* The parent executes the wait.
35
*/
while (wait(&status) != pid)
/* empty */ ;
}
Exerises
Exercise 12727
Use popen() to pipe the rwho (UNIX command) output into more (UNIX command) in a C
program.
36
Interprocess Communication (IPC), Pipes
We have now began to see how multiple processes may be running on a machine and maybe be
controlled (spawned by fork() by one of our programs.
In numerous applications there is clearly a need for these processes to communicate with each
exchanging data or control information. There are a few methods which can accomplish this
task. We will consider:
• Pipes
• Signals
• Message Queues
• Semaphores
• Shared Memory
• Sockets
In this chapter, we will study the piping of two processes. We will study the others in turn in
subsequent chapters.
37
pipe() returns 0 on success, -1 on failure and sets errno accordingly.
The standard programming model is that after the pipe has been set up, two (or more)
cooperative processes will be created by a fork and data will be passed using read() and
write().
Pipes opened with pipe() should be closed with close(int fd).
Example: Parent writes to a child
int pdes[2];
pipe(pdes);
if ( fork() == 0 )
{ /* child */
close(pdes[1]); /* not required */
read( pdes[0]); /* read from parent */
.....
}
else
{ close(pdes[0]); /* not required */
write( pdes[1]); /* write to child */
.....
}
#include "externals.h"
#include <signal.h>
38
double drand48();
void quit();
main()
{ float i;
float y1,y2,y3,y4;
/* load files */
fprintf(fp1,"%f %f\n",i,y1);
fprintf(fp2,"%f %f\n",i,y2);
fprintf(fp3,"%f %f\n",i,y3);
fprintf(fp4,"%f %f\n",i,y4);
/* plot graph */
PlotOne();
usleep(250); /* sleep for short time */
}
}
void quit()
{ printf("\nctrl-c caught:\n Shutting down pipes\n");
StopPlot();
39
fclose(fp4);
#include "externals.h"
void
StartPlot(void)
{ plot1 = popen(command1, "w");
fprintf(plot1, "%s", set_term);
fflush(plot1);
if (plot1 == NULL)
exit(2);
plot2 = popen(command2, "w");
fprintf(plot2, "%s", set_term);
fflush(plot2);
if (plot2 == NULL)
exit(2);
}
void
RemoveDat(void)
{ ashell = popen(deletefiles, "w");
exit(0);
}
void
StopPlot(void)
{ pclose(plot1);
pclose(plot2);
}
void
40
PlotOne(void)
{ fprintf(plot1, "%s", startplot1);
fflush(plot1);
void
RePlot(void)
{ fprintf(plot1, "%s", replot);
fflush(plot1);
}
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/* prototypes */
void StartPlot(void);
void RemoveDat(void);
void StopPlot(void);
void PlotOne(void);
void RePlot(void);
#endif
Exercises
Exercise 12733
Setup a two-way pipe between parent and child processes in a C program. i.e. both can send and
receive signals.
41
IPC:Interrupts and Signals: <signal.h>
In this section will look at ways in which two processes can communicate. When a process
terminates abnormally it usually tries to send a signal indicating what went wrong. C programs
(and UNIX) can trap these for diagnostics. Also user specified communication can take place in
this way.
Signals are software generated interrupts that are sent to a process when a event happens. Signals
can be synchronously generated by an error in an application, such as SIGFPE and SIGSEGV,
but most signals are asynchronous. Signals can be posted to a process when the system detects a
software event, such as a user entering an interrupt or stop or a kill request from another process.
Signals can also be come directly from the OS kernel when a hardware event such as a bus error
or an illegal instruction is encountered. The system defines a set of signals that can be posted to
a process. Signal delivery is analogous to hardware interrupts in that a signal can be blocked
from being delivered in the future. Most signals cause termination of the receiving process if no
action is taken by the process in response to the signal. Some signals stop the receiving process
and other signals can be ignored. Each signal has a default action which is one of the following:
• The signal is discarded after being received
• The process is terminated after the signal is received
• A core file is written, then the process is terminated
• Stop the process after the signal is received
Each signal defined by the system falls into one of five classes:
• Hardware conditions
• Software conditions
• Input/output notification
• Process control
• Resource control
Macros are defined in <signal.h> header file for common signals.
These include:
SIGHUP 1 /* hangup */ SIGINT 2 /* interrupt */
SIGQUIT 3 /* quit */ SIGILL 4 /* illegal instruction */
SIGABRT 6 /* used by abort */ SIGKILL 9 /* hard kill */
SIGALRM 14 /* alarm clock */
SIGCONT 19 /* continue a stopped process */
SIGCHLD 20 /* to parent on child stop or exit
*/
Signals can be numbered from 0 to 31.
42
int kill(int pid, int signal) - a system call that send a signal to a process,
pid. If pid is greater than zero, the signal is sent to the process whose process ID is equal to pid.
If pid is 0, the signal is sent to all processes, except system processes.
kill() returns 0 for a successful call, -1 otherwise and sets errno accordingly.
int raise(int sig) sends the signal sig to the executing program. raise() actually
uses kill() to send the signal to the executing program:
kill(getpid(), sig);
There is also a UNIX command called kill that can be used to send signals from the command
line - see man pages.
NOTE: that unless caught or ignored, the kill signal terminates the process. Therefore
protection is built into the system.
Only processes with certain access privileges can be killed off.
Basic rule: only processes that have the same user can send/receive messages.
The SIGKILL signal cannot be caught or ignored and will always terminate a process.
43
func() can have three values:
SIG_DFL
-- a pointer to a system default function SID_DFL(), which will terminate the process
upon receipt of sig.
SIG_IGN
-- a pointer to system ignore function SIG_IGN() which will disregard the sig action
(UNLESS it is SIGKILL).
A function address
-- a user specified function.
SIG_DFL and SIG_IGN are defined in signal.h (standard library) header file.
Thus to ignore a ctrl-c command from the command line. we could do:
signal(SIGINT, SIG_IGN);
TO reset system so that SIGINT causes a termination at any place in our program, we would do:
signal(SIGINT, SIG_DFL);
So lets write a program to trap a ctrl-c but not quit on this signal. We have a function
sigproc() that is executed when we trap a ctrl-c. We will also set another function to quit
the program if it traps the SIGQUIT signal so we can terminate our program:
#include <stdio.h>
void sigproc(void);
void quitproc(void);
main()
{ signal(SIGINT, sigproc);
signal(SIGQUIT, quitproc);
void sigproc()
{ signal(SIGINT, sigproc); /* */
/* NOTE some versions of UNIX will reset signal to default
after each call. So for portability reset signal each time
*/
void quitproc()
44
exit(0); /* normal exit status */
}
/* cc sig_talk.c -o sig_talk */
#include <stdio.h>
#include <signal.h>
main()
{ int pid;
if (pid == 0)
{ /* child */
signal(SIGHUP,sighup); /* set function calls */
signal(SIGINT,sigint);
signal(SIGQUIT, sigquit);
for(;;); /* loop for ever */
}
else /* parent */
{ /* pid hold id of child */
printf("\nPARENT: sending SIGHUP\n\n");
kill(pid,SIGHUP);
sleep(3); /* pause for 3 secs */
printf("\nPARENT: sending SIGINT\n\n");
45
kill(pid,SIGINT);
sleep(3); /* pause for 3 secs */
printf("\nPARENT: sending SIGQUIT\n\n");
kill(pid,SIGQUIT);
sleep(3);
}
}
void sighup()
void sigint()
void sigquit()
46
IPC:Message Queues:<sys/msg.h>
The basic idea of a message queue is a simple one.
Two (or more) processes can exchange information via access to a common system message
queue. The sending process places via some (OS) message-passing module a message onto a
queue which can be read by another process (Figure 24.1). Each message is given an
identification or type so that processes can select the appropriate message. Process must share
a common key in order to gain access to the queue in the first place (subject to other
permissions -- see below).
Fig. 24.1 Basic Message Passing IPC messaging lets processes send and receive messages, and
queue messages for processing in an arbitrary order. Unlike the file byte-stream data flow of
pipes, each IPC message has an explicit length. Messages can be assigned a specific type.
Because of this, a server process can direct message traffic between clients on its queue by using
the client process PID as the message type. For single-message transactions, multiple server
processes can work in parallel on transactions sent to a shared message queue.
Before a process can send or receive a message, the queue must be initialized (through the
msgget function see below) Operations to send and receive messages are performed by the
msgsnd() and msgrcv() functions, respectively.
When a message is sent, its text is copied to the message queue. The msgsnd() and
msgrcv() functions can be performed as either blocking or non-blocking operations. Non-
blocking operations allow for asynchronous message transfer -- the process is not suspended as a
result of sending or receiving a message. In blocking or synchronous message passing the
sending process cannot continue until the message has been transferred or has even been
acknowledged by a receiver. IPC signal and other mechanisms can be employed to implement
such transfer. A blocked message operation remains suspended until one of the following three
47
conditions occurs:
• The call succeeds.
• The process receives a signal.
• The queue is removed.
It can also return the message queue ID (msqid) of the queue corresponding to the key
argument. The value passed as the msgflg argument must be an octal integer with settings for
the queue's permissions and control flags.
The following code illustrates the msgget() function.
#include <sys/ipc.h>;
#include <sys/msg.h>;
...
...
key = ...
msgflg = ...
48
use this ID. If the key argument is specified as IPC_PRIVATE, the call initializes a new
instance of an IPC facility that is private to the creating process. When the IPC_CREAT flag is
supplied in the flags argument appropriate to the call, the function tries to create the facility if it
does not exist already. When called with both the IPC_CREAT and IPC_EXCL flags, the
function fails if the facility already exists. This can be useful when more than one process might
attempt to initialize the facility. One such case might involve several server processes having
access to the same facility. If they all attempt to create the facility with IPC_EXCL in effect,
only the first attempt succeeds. If neither of these flags is given and the facility already exists,
the functions to get access simply return the ID of the facility. If IPC_CREAT is omitted and the
facility is not already initialized, the calls fail. These control flags are combined, using logical
(bitwise) OR, with the octal permission modes to form the flags argument. For example, the
statement below initializes a new message queue if the queue does not exist.
msqid = msgget(ftok("/tmp",
key), (IPC_CREAT | IPC_EXCL | 0400));
The first argument evaluates to a key based on the string ("/tmp"). The second argument
evaluates to the combined permissions and control flags.
The msqid argument must be the ID of an existing message queue. The cmd argument is
one of:
IPC_STAT
-- Place information about the status of the queue in the data structure pointed to by buf.
The process must have read permission for this call to succeed.
IPC_SET
-- Set the owner's user and group ID, the permissions, and the size (in number of bytes) of
the message queue. A process must have the effective user ID of the owner, creator, or
superuser for this call to succeed.
IPC_RMID
-- Remove the message queue specified by the msqid argument.
The following code illustrates the msgctl() function with all its various flags:
#include<sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
...
if (msgctl(msqid, IPC_STAT, &buf) == -1) {
perror("msgctl: msgctl failed");
exit(1);
49
}
...
if (msgctl(msqid, IPC_SET, &buf) == -1) {
perror("msgctl: msgctl failed");
exit(1);
}
...
The msqid argument must be the ID of an existing message queue. The msgp argument is a
pointer to a structure that contains the type of the message and its text. The structure below is an
example of what this user-defined buffer might look like:
struct mymsg {
long mtype; /* message type */
char mtext[MSGSZ]; /* message text of length MSGSZ */
}
50
•msg_qnum is incremented by 1.
• msg_lspid is set equal to the process ID of the calling process.
• msg_stime is set equal to the current time.
The following code illustrates msgsnd() and msgrcv():
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
...
...
if (msgp == NULL) {
(void) fprintf(stderr, "msgop: %s %d byte messages.\n",
"could not allocate message buffer for", maxmsgsz);
exit(1);
...
msgsz = ...
msgflg = ...
51
mq_receive() -- Receives (removes) the oldest, highest priority message from the queue.
mq_notify() -- Notifies a process or thread that a message is available in the queue.
mq_setattr() -- Set or get message queue attributes.
The basic operation of these functions is as described above. For full function prototypes and
further information see the UNIX man pages
/*
* Declare the message structure.
*/
main()
{
int msqid;
int msgflg = IPC_CREAT | 0666;
key_t key;
message_buf sbuf;
size_t buf_length;
/*
52
* Get the message queue id for the
* "name" 1234, which was created by
* the server.
*/
key = 1234;
/*
* We'll send message type 1
*/
sbuf.mtype = 1;
buf_length = strlen(sbuf.mtext) + 1 ;
/*
* Send a message.
*/
if (msgsnd(msqid, &sbuf, buf_length, IPC_NOWAIT) < 0) {
printf ("%d, %d, %s, %d\n", msqid, sbuf.mtype, sbuf.mtext,
buf_length);
perror("msgsnd");
exit(1);
}
else
printf("Message: \"%s\" Sent\n", sbuf.mtext);
exit(0);
}
53
message_rec.c -- receiving the above message
The full code listing for message_send.c's companion process, message_rec.c is as
follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
/*
* Declare the message structure.
*/
main()
{
int msqid;
key_t key;
message_buf rbuf;
/*
* Get the message queue id for the
* "name" 1234, which was created by
* the server.
*/
key = 1234;
/*
* Receive an answer of message type 1.
*/
if (msgrcv(msqid, &rbuf, MSGSZ, 1, 0) < 0) {
perror("msgrcv");
exit(1);
}
/*
* Print the answer.
*/
printf("%s\n", rbuf.mtext);
exit(0);
}
54
The essential points to note here are:
• The Message queue is opened with msgget (message flag 0666) and the same key as
message_send.c.
• A message of the same type 1 is received from the queue with the message ``Did you
get this?'' stored in rbuf.mtext.
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
main()
{
key_t key; /* key to be passed to msgget() */
int msgflg, /* msgflg to be passed to msgget() */
msqid; /* return value from msgget() */
(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
(void) fprintf(stderr, "IPC_PRIVATE == %#lx\n", IPC_PRIVATE);
(void) fprintf(stderr, "Enter key: ");
(void) scanf("%li", &key);
(void) fprintf(stderr, "\nExpected flags for msgflg argument
are:\n");
(void) fprintf(stderr, "\tIPC_EXCL =\t%#8.8o\n", IPC_EXCL);
(void) fprintf(stderr, "\tIPC_CREAT =\t%#8.8o\n", IPC_CREAT);
55
(void) fprintf(stderr, "\towner read =\t%#8.8o\n", 0400);
(void) fprintf(stderr, "\towner write =\t%#8.8o\n", 0200);
(void) fprintf(stderr, "\tgroup read =\t%#8.8o\n", 040);
(void) fprintf(stderr, "\tgroup write =\t%#8.8o\n", 020);
(void) fprintf(stderr, "\tother read =\t%#8.8o\n", 04);
(void) fprintf(stderr, "\tother write =\t%#8.8o\n", 02);
(void) fprintf(stderr, "Enter msgflg value: ");
(void) scanf("%i", &msgflg);
main()
{
struct msqid_ds buf; /* queue descriptor buffer for IPC_STAT
and IP_SET commands */
int cmd, /* command to be given to msgctl() */
msqid; /* queue ID to be given to msgctl() */
56
(void fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
/* Get the msqid and cmd arguments for the msgctl() call. */
(void) fprintf(stderr,
"Please enter arguments for msgctls() as requested.");
(void) fprintf(stderr, "\nEnter the msqid: ");
(void) scanf("%i", &msqid);
(void) fprintf(stderr, "\tIPC_RMID = %d\n", IPC_RMID);
(void) fprintf(stderr, "\tIPC_SET = %d\n", IPC_SET);
(void) fprintf(stderr, "\tIPC_STAT = %d\n", IPC_STAT);
(void) fprintf(stderr, "\nEnter the value for the command: ");
(void) scanf("%i", &cmd);
switch (cmd) {
case IPC_SET:
/* Modify settings in the message queue control structure.
*/
(void) fprintf(stderr, "Before IPC_SET, get current
values:");
/* fall through to IPC_STAT processing */
case IPC_STAT:
/* Get a copy of the current message queue control
* structure and show it to the user. */
do_msgctl(msqid, IPC_STAT, &buf);
(void) fprintf(stderr, ]
"msg_perm.uid = %d\n", buf.msg_perm.uid);
(void) fprintf(stderr,
"msg_perm.gid = %d\n", buf.msg_perm.gid);
(void) fprintf(stderr,
"msg_perm.cuid = %d\n", buf.msg_perm.cuid);
(void) fprintf(stderr,
"msg_perm.cgid = %d\n", buf.msg_perm.cgid);
(void) fprintf(stderr, "msg_perm.mode = %#o, ",
buf.msg_perm.mode);
(void) fprintf(stderr, "access permissions = %#o\n",
buf.msg_perm.mode & 0777);
(void) fprintf(stderr, "msg_cbytes = %d\n",
buf.msg_cbytes);
(void) fprintf(stderr, "msg_qbytes = %d\n",
buf.msg_qbytes);
(void) fprintf(stderr, "msg_qnum = %d\n", buf.msg_qnum);
(void) fprintf(stderr, "msg_lspid = %d\n",
buf.msg_lspid);
(void) fprintf(stderr, "msg_lrpid = %d\n",
buf.msg_lrpid);
(void) fprintf(stderr, "msg_stime = %s", buf.msg_stime ?
ctime(&buf.msg_stime) : "Not Set\n");
(void) fprintf(stderr, "msg_rtime = %s", buf.msg_rtime ?
ctime(&buf.msg_rtime) : "Not Set\n");
(void) fprintf(stderr, "msg_ctime = %s",
ctime(&buf.msg_ctime));
if (cmd == IPC_STAT)
break;
57
/* Now continue with IPC_SET. */
(void) fprintf(stderr, "Enter msg_perm.uid: ");
(void) scanf ("%hi", &buf.msg_perm.uid);
(void) fprintf(stderr, "Enter msg_perm.gid: ");
(void) scanf("%hi", &buf.msg_perm.gid);
(void) fprintf(stderr, "%s\n", warning_message);
(void) fprintf(stderr, "Enter msg_perm.mode: ");
(void) scanf("%hi", &buf.msg_perm.mode);
(void) fprintf(stderr, "Enter msg_qbytes: ");
(void) scanf("%hi", &buf.msg_qbytes);
do_msgctl(msqid, IPC_SET, &buf);
break;
case IPC_RMID:
default:
/* Remove the message queue or try an unknown command. */
do_msgctl(msqid, cmd, (struct msqid_ds *)NULL);
break;
}
exit(0);
}
/*
* Print indication of arguments being passed to msgctl(), call
* msgctl(), and report the results. If msgctl() fails, do not
* return; this example doesn't deal with errors, it just reports
* them.
*/
static void
do_msgctl(msqid, cmd, buf)
struct msqid_ds *buf; /* pointer to queue descriptor buffer */
int cmd, /* command code */
msqid; /* queue ID */
{
register int rtrn; /* hold area for return value from msgctl()
*/
58
* routines. It allows the user to attempt to send and receive as
many
* messages as wanted to or from one message queue.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
main()
{
register int c; /* message text input */
int choice; /* user's selected operation code */
register int i; /* loop control for mtext */
int msgflg; /* message flags for the operation */
struct msgbuf *msgp; /* pointer to the message buffer */
int msgsz; /* message size */
long msgtyp; /* desired message type */
int msqid, /* message queue ID to be used */
maxmsgsz, /* size of allocated message buffer */
rtrn; /* return value from msgrcv or msgsnd */
(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
/* Get the message queue ID and set up the message buffer. */
(void) fprintf(stderr, "Enter msqid: ");
(void) scanf("%i", &msqid);
/*
* Note that <sys/msg.h> includes a definition of struct
msgbuf
* with the mtext field defined as:
* char mtext[1];
* therefore, this definition is only a template, not a
structure
* definition that you can use directly, unless you want only
to
* send and receive messages of 0 or 1 byte. To handle this,
* malloc an area big enough to contain the template - the size
* of the mtext template field + the size of the mtext field
* wanted. Then you can use the pointer returned by malloc as a
* struct msgbuf with an mtext field of the size you want. Note
* also that sizeof msgp->mtext is valid even though msgp
isn't
* pointing to anything yet. Sizeof doesn't dereference msgp,
but
59
* uses its type to figure out what you are asking about.
*/
(void) fprintf(stderr,
"Enter the message buffer size you want:");
(void) scanf("%i", &maxmsgsz);
if (maxmsgsz < 0) {
(void) fprintf(stderr, "msgop: %s\n",
"The message buffer size must be >= 0.");
exit(1);
}
msgp = (struct msgbuf *)malloc((unsigned)(sizeof(struct
msgbuf)
- sizeof msgp->mtext + maxmsgsz));
if (msgp == NULL) {
(void) fprintf(stderr, "msgop: %s %d byte messages.\n",
"could not allocate message buffer for", maxmsgsz);
exit(1);
}
/* Loop through message operations until the user is ready to
quit. */
while (choice = ask()) {
switch (choice) {
case 1: /* msgsnd() requested: Get the arguments, make the
call, and report the results. */
(void) fprintf(stderr, "Valid msgsnd message %s\n",
"types are positive integers.");
(void) fprintf(stderr, "Enter msgp->mtype: ");
(void) scanf("%li", &msgp->mtype);
if (maxmsgsz) {
/* Since you've been using scanf, you need the loop
below to throw away the rest of the input on the
line after the entered mtype before you start
reading the mtext. */
while ((c = getchar()) != '\n' && c != EOF);
(void) fprintf(stderr, "Enter a %s:\n",
"one line message");
for (i = 0; ((c = getchar()) != '\n'); i++) {
if (i >= maxmsgsz) {
(void) fprintf(stderr, "\n%s\n", full_buf);
while ((c = getchar()) != '\n');
break;
}
msgp->mtext[i] = c;
}
msgsz = i;
} else
msgsz = 0;
(void) fprintf(stderr,"\nMeaningful msgsnd flag is:\n");
(void) fprintf(stderr, "\tIPC_NOWAIT =\t%#8.8o\n",
IPC_NOWAIT);
(void) fprintf(stderr, "Enter msgflg: ");
(void) scanf("%i", &msgflg);
(void) fprintf(stderr, "%s(%d, msgp, %d, %#o)\n",
"msgop: Calling msgsnd", msqid, msgsz, msgflg);
(void) fprintf(stderr, "msgp->mtype = %ld\n",
msgp->mtype);
(void) fprintf(stderr, "msgp->mtext = \"");
for (i = 0; i < msgsz; i++)
60
(void) fputc(msgp->mtext[i], stderr);
(void) fprintf(stderr, "\"\n");
rtrn = msgsnd(msqid, msgp, msgsz, msgflg);
if (rtrn == -1)
perror("msgop: msgsnd failed");
else
(void) fprintf(stderr,
"msgop: msgsnd returned %d\n", rtrn);
break;
case 2: /* msgrcv() requested: Get the arguments, make the
call, and report the results. */
for (msgsz = -1; msgsz < 0 || msgsz > maxmsgsz;
(void) scanf("%i", &msgsz))
(void) fprintf(stderr, "%s (0 <= msgsz <= %d): ",
"Enter msgsz", maxmsgsz);
(void) fprintf(stderr, "msgtyp meanings:\n");
(void) fprintf(stderr, "\t 0 %s\n", first_on_queue);
(void) fprintf(stderr, "\t>0 %s of given type\n",
first_on_queue);
(void) fprintf(stderr, "\t<0 %s with type <= |msgtyp|\n",
first_on_queue);
(void) fprintf(stderr, "Enter msgtyp: ");
(void) scanf("%li", &msgtyp);
(void) fprintf(stderr,
"Meaningful msgrcv flags are:\n");
(void) fprintf(stderr, "\tMSG_NOERROR =\t%#8.8o\n",
MSG_NOERROR);
(void) fprintf(stderr, "\tIPC_NOWAIT =\t%#8.8o\n",
IPC_NOWAIT);
(void) fprintf(stderr, "Enter msgflg: ");
(void) scanf("%i", &msgflg);
(void) fprintf(stderr, "%s(%d, msgp, %d, %ld, %#o);\n",
"msgop: Calling msgrcv", msqid, msgsz,
msgtyp, msgflg);
rtrn = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
if (rtrn == -1)
perror("msgop: msgrcv failed");
else {
(void) fprintf(stderr, "msgop: %s %d\n",
"msgrcv returned", rtrn);
(void) fprintf(stderr, "msgp->mtype = %ld\n",
msgp->mtype);
(void) fprintf(stderr, "msgp->mtext is: \"");
for (i = 0; i < rtrn; i++)
(void) fputc(msgp->mtext[i], stderr);
(void) fprintf(stderr, "\"\n");
}
break;
default:
(void) fprintf(stderr, "msgop: operation unknown\n");
break;
}
}
exit(0);
}
/*
* Ask the user what to do next. Return the user's choice code.
61
* Don't return until the user selects a valid choice.
*/
static
ask()
{
int response; /* User's response. */
do {
(void) fprintf(stderr, "Your options are:\n");
(void) fprintf(stderr, "\tExit =\t0 or Control-D\n");
(void) fprintf(stderr, "\tmsgsnd =\t1\n");
(void) fprintf(stderr, "\tmsgrcv =\t2\n");
(void) fprintf(stderr, "Enter your choice: ");
return(response);
}
Exercises
Exercise 12755
Write a 2 programs that will both send and messages and construct the following dialog between
them
• (Process 1) Sends the message "Are you hearing me?"
• (Process 2) Receives the message and replies "Loud and Clear".
• (Process 1) Receives the reply and then says "I can hear you too".
Exercise 12756
Compile the programs msgget.c, msgctl.c and msgop.c and then
• investigate and understand fully the operations of the flags (access, creation etc.
permissions) you can set interactively in the programs.
• Use the programs to:
• Send and receive messages of two different message types.
• Place several messages on the queue and inquire about the state of the queue with
msgctl.c. Add/delete a few messages (using msgop.c and perform the inquiry once
more.
• Use msgctl.c to alter a message on the queue.
• Use msgctl.c to delete a message from the queue.
Exercise 12757
Write a server program and two client programs so that the server can communicate privately to
each client individually via a single message queue.
Exercise 12758
Implement a blocked or synchronous method of message passing using signal interrupts
62
63
IPC:Semaphores
Semaphores are a programming construct designed by E. W. Dijkstra in the late 1960s.
Dijkstra's model was the operation of railroads: consider a stretch of railroad in which there is a
single track over which only one train at a time is allowed. Guarding this track is a semaphore. A
train must wait before entering the single track until the semaphore is in a state that permits
travel. When the train enters the track, the semaphore changes state to prevent other trains from
entering the track. A train that is leaving this section of track must again change the state of the
semaphore to allow another train to enter. In the computer version, a semaphore appears to be a
simple integer. A process (or a thread) waits for permission to proceed by waiting for the integer
to become 0. The signal if it proceeds signals that this by performing incrementing the integer by
1. When it is finished, the process changes the semaphore's value by subtracting one from it.
Semaphores let processes query or alter status information. They are often used to monitor and
control the availability of system resources such as shared memory segments.
Semaphores can be operated on as individual units or as elements in a set. Because System V
IPC semaphores can be in a large array, they are extremely heavy weight. Much lighter weight
semaphores are available in the threads library (see man semaphore and also Chapter 30.3)
and POSIX semaphores (see below briefly). Threads library semaphores must be used with
mapped memory . A semaphore set consists of a control structure and an array of individual
semaphores. A set of semaphores can contain up to 25 elements.
In a similar fashion to message queues, the semaphore set must be initialized using semget();
the semaphore creator can change its ownership or permissions using semctl(); and
semaphore operations are performed via the semop() function. These are now discussed
below:
...
64
key_t key; /* key to pass to semget() */
int semflg; /* semflg to pass tosemget() */
int nsems; /* nsems to pass to semget() */
int semid; /* return value from semget() */
...
key = ...
nsems = ...
semflg = ... ...
if ((semid = semget(key, nsems, semflg)) == -1) {
perror("semget: semget failed");
exit(1); }
else
...
Controlling Semaphores
semctl() changes permissions and other characteristics of a semaphore set. It is prototyped as
follows:
int semctl(int semid, int semnum, int cmd, union semun arg);
It must be called with a valid semaphore ID, semid. The semnum value selects a semaphore
within an array by its index. The cmd argument is one of the following control flags:
GETVAL
-- Return the value of a single semaphore.
SETVAL
-- Set the value of a single semaphore. In this case, arg is taken as arg.val, an int.
GETPID
-- Return the PID of the process that performed the last operation on the semaphore or
array.
GETNCNT
-- Return the number of processes waiting for the value of a semaphore to increase.
GETZCNT
-- Return the number of processes waiting for the value of a particular semaphore to reach
zero.
GETALL
-- Return the values for all semaphores in a set. In this case, arg is taken as arg.array,
a pointer to an array of unsigned shorts (see below).
SETALL
-- Set values for all semaphores in a set. In this case, arg is taken as arg.array, a
pointer to an array of unsigned shorts.
IPC_STAT
-- Return the status information from the control structure for the semaphore set and place
it in the data structure pointed to by arg.buf, a pointer to a buffer of type semid_ds.
IPC_SET
-- Set the effective user and group identification and permissions. In this case, arg is
65
taken as arg.buf.
IPC_RMID
-- Remove the specified semaphore set.
A process must have an effective user identification of owner, creator, or superuser to perform an
IPC_SET or IPC_RMID command. Read and write permission is required as for the other
control commands. The following code illustrates semctl ().
The fourth argument union semun arg is optional, depending upon the operation requested.
If required it is of type union semun, which must be explicitly declared by the application
program as:
union semun {
int val;
struct semid_ds *buf;
ushort *array;
} arg;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
ushort *array;
} arg;
int i;
int semnum = ....;
int cmd = GETALL; /* get value */
...
i = semctl(semid, semnum, cmd, arg);
if (i == -1) {
perror("semctl: semctl failed");
exit(1);
}
else
...
Semaphore Operations
semop() performs operations on a semaphore set. It is prototyped by:
int semop(int semid, struct sembuf *sops, size_t nsops);
The semid argument is the semaphore ID returned by a previous semget() call. The sops
argument is a pointer to an array of structures, each containing the following information about a
semaphore operation:
66
• The semaphore number
• The operation to be performed
• Control flags, if any.
The sembuf structure specifies a semaphore operation, as defined in <sys/sem.h>.
struct sembuf {
ushort_t sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
The nsops argument specifies the length of the array, the maximum size of which is determined
by the SEMOPM configuration option; this is the maximum number of operations allowed by a
single semop() call, and is set to 10 by default. The operation to be performed is determined as
follows:
• A positive integer increments the semaphore value by that amount.
• A negative integer decrements the semaphore value by that amount. An attempt to set a
semaphore to a value less than zero fails or blocks, depending on whether IPC_NOWAIT
is in effect.
• A value of zero means to wait for the semaphore value to reach zero.
There are two control flags that can be used with semop():
IPC_NOWAIT
-- Can be set for any operations in the array. Makes the function return without changing
any semaphore value if any operation for which IPC_NOWAIT is set cannot be performed.
The function fails if it tries to decrement a semaphore more than its current value, or tests a
nonzero semaphore to be equal to zero.
SEM_UNDO
-- Allows individual operations in the array to be undone when the process exits.
This function takes a pointer, sops, to an array of semaphore operation structures. Each
structure in the array contains data about an operation to perform on a semaphore. Any process
with read permission can test whether a semaphore has a zero value. To increment or decrement
a semaphore requires write permission. When an operation fails, none of the semaphores is
altered.
The process blocks (unless the IPC_NOWAIT flag is set), and remains blocked until:
• the semaphore operations can all finish, so the call succeeds,
• the process receives a signal, or
• the semaphore set is removed.
Only one process at a time can update a semaphore. Simultaneous requests by different
processes are performed in an arbitrary order. When an array of operations is given by a
semop() call, no updates are done until all operations on the array can finish successfully.
If a process with exclusive use of a semaphore terminates abnormally and fails to undo the
operation or free the semaphore, the semaphore stays locked in memory in the state the process
left it. To prevent this, the SEM_UNDO control flag makes semop() allocate an undo structure
for each semaphore operation, which contains the operation that returns the semaphore to its
67
previous state. If the process dies, the system applies the operations in the undo structures. This
prevents an aborted process from leaving a semaphore set in an inconsistent state. If processes
share access to a resource controlled by a semaphore, operations on the semaphore should not be
made with SEM_UNDO in effect. If the process that currently has control of the resource
terminates abnormally, the resource is presumed to be inconsistent. Another process must be able
to recognize this to restore the resource to a consistent state. When performing a semaphore
operation with SEM_UNDO in effect, you must also have it in effect for the call that will perform
the reversing operation. When the process runs normally, the reversing operation updates the
undo structure with a complementary value. This ensures that, unless the process is aborted, the
values applied to the undo structure are cancel to zero. When the undo structure reaches zero, it
is removed.
NOTE:Using SEM_UNDO inconsistently can lead to excessive resource consumption because
allocated undo structures might not be freed until the system is rebooted.
The following code illustrates the semop() function:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
...
int i;
int nsops; /* number of operations to do */
int semid; /* semid of semaphore set */
struct sembuf *sops; /* ptr to operations to perform */
...
68
sem_destroy() -- Initializes a semaphore structure (internal to the calling program, so not a
named semaphore).
sem_getvalue() -- Copies the value of the semaphore into the specified integer.
sem_wait(), sem_trywait() -- Blocks while the semaphore is held by other processes
or returns an error if the semaphore is held by another process.
sem_post() -- Increments the count of the semaphore.
The basic operation of these functions is essence the same as described above, except note there
are more specialised functions, here. These are not discussed further here and the reader is
referred to the online man pages for further details.
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
ushort *array;
};
main()
{ int i,j;
int pid;
int semid; /* semid of semaphore set */
key_t key = 1234; /* key to pass to semget() */
int semflg = IPC_CREAT | 0666; /* semflg to pass to semget() */
int nsems = 1; /* nsems to pass to semget() */
int nsops; /* number of operations to do */
struct sembuf *sops = (struct sembuf *) malloc(2*sizeof(struct sembuf));
/* ptr to operations to perform */
/* set up semaphore */
69
} else
(void) fprintf(stderr, "semget: semget succeeded: semid =\
%d\n", semid);
if (pid == 0)
{ /* child */
i = 0;
nsops = 2;
sops[1].sem_num = 0;
sops[1].sem_op = 1; /* increment semaphore -- take control of track */
sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off semaphore */
nsops = 1;
70
/* wait for semaphore to reach zero */
sops[0].sem_num = 0;
sops[0].sem_op = -1; /* Give UP COntrol of track */
sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off
semaphore, asynchronous */
}
else /* parent */
{ /* pid hold id of child */
i = 0;
nsops = 2;
sops[1].sem_num = 0;
sops[1].sem_op = 1; /* increment semaphore -- take control of track */
sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off semaphore */
71
(void) fprintf(stderr, "semop: semop returned %d\n", j);
nsops = 1;
}
}
72
and semop.c respectively.
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
main()
{
key_t key; /* key to pass to semget() */
int semflg; /* semflg to pass to semget() */
int nsems; /* nsems to pass to semget() */
int semid; /* return value from semget() */
(void) fprintf(stderr,
"All numeric input must follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
(void) fprintf(stderr, "IPC_PRIVATE == %#lx\n", IPC_PRIVATE);
(void) fprintf(stderr, "Enter key: ");
(void) scanf("%li", &key);
73
%d\n",
semid);
exit(0);
}
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <time.h>
main()
{
union semun arg; /* union to pass to semctl() */
int cmd, /* command to give to semctl() */
i, /* work area */
semid, /* semid to pass to semctl() */
semnum; /* semnum to pass to semctl() */
(void) fprintf(stderr,
"All numeric input must follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
(void) fprintf(stderr, "Enter semid value: ");
(void) scanf("%i", &semid);
74
(void) fprintf(stderr, "\tGETNCNT = %d\n", GETNCNT);
(void) fprintf(stderr, "\tGETPID = %d\n", GETPID);
(void) fprintf(stderr, "\tGETVAL = %d\n", GETVAL);
(void) fprintf(stderr, "\tGETZCNT = %d\n", GETZCNT);
(void) fprintf(stderr, "\tIPC_RMID = %d\n", IPC_RMID);
(void) fprintf(stderr, "\tIPC_SET = %d\n", IPC_SET);
(void) fprintf(stderr, "\tIPC_STAT = %d\n", IPC_STAT);
(void) fprintf(stderr, "\tSETALL = %d\n", SETALL);
(void) fprintf(stderr, "\tSETVAL = %d\n", SETVAL);
(void) fprintf(stderr, "\nEnter cmd: ");
(void) scanf("%i", &cmd);
75
/* Get PID of last process to successfully complete a
semctl(SETVAL), semctl(SETALL), or semop() on the
semaphore. */
arg.val = 0;
do_semctl(semid, 0, GETPID, arg);
break;
case GETNCNT:
/* Get number of processes waiting for semaphore value to
increase. */
arg.val = 0;
do_semctl(semid, semnum, GETNCNT, arg);
break;
case GETZCNT:
/* Get number of processes waiting for semaphore value to
become zero. */
arg.val = 0;
do_semctl(semid, semnum, GETZCNT, arg);
break;
case SETALL:
/* Set the values of all semaphores in the set. */
(void) fprintf(stderr,
"There are %d semaphores in the set.\n",
semid_ds.sem_nsems);
(void) fprintf(stderr, "Enter semaphore values:\n");
for (i = 0; i < semid_ds.sem_nsems; i++) {
(void) fprintf(stderr, "Semaphore %d: ", i);
(void) scanf("%hi", &arg.array[i]);
}
do_semctl(semid, 0, SETALL, arg);
/* Fall through to verify the results. */
(void) fprintf(stderr,
"Do semctl GETALL command to verify results.\n");
case GETALL:
/* Get and print the values of all semaphores in the
set.*/
do_semctl(semid, 0, GETALL, arg);
(void) fprintf(stderr,
"The values of the %d semaphores are:\n",
semid_ds.sem_nsems);
for (i = 0; i < semid_ds.sem_nsems; i++)
(void) fprintf(stderr, "%d ", arg.array[i]);
(void) fprintf(stderr, "\n");
break;
case IPC_SET:
/* Modify mode and/or ownership. */
arg.buf = &semid_ds;
do_semctl(semid, 0, IPC_STAT, arg);
(void) fprintf(stderr, "Status before IPC_SET:\n");
do_stat();
(void) fprintf(stderr, "Enter sem_perm.uid value: ");
(void) scanf("%hi", &semid_ds.sem_perm.uid);
(void) fprintf(stderr, "Enter sem_perm.gid value: ");
(void) scanf("%hi", &semid_ds.sem_perm.gid);
(void) fprintf(stderr, "%s\n", warning_message);
(void) fprintf(stderr, "Enter sem_perm.mode value: ");
(void) scanf("%hi", &semid_ds.sem_perm.mode);
do_semctl(semid, 0, IPC_SET, arg);
/* Fall through to verify changes. */
76
(void) fprintf(stderr, "Status after IPC_SET:\n");
case IPC_STAT:
/* Get and print current status. */
arg.buf = &semid_ds;
do_semctl(semid, 0, IPC_STAT, arg);
do_stat();
break;
case IPC_RMID:
/* Remove the semaphore set. */
arg.val = 0;
do_semctl(semid, 0, IPC_RMID, arg);
break;
default:
/* Pass unknown command to semctl. */
arg.val = 0;
do_semctl(semid, 0, cmd, arg);
break;
}
exit(0);
}
/*
* Print indication of arguments being passed to semctl(), call
* semctl(), and report the results. If semctl() fails, do not
* return; this example doesn't deal with errors, it just reports
* them.
*/
static void
do_semctl(semid, semnum, cmd, arg)
union semun arg;
int cmd,
semid,
semnum;
{
register int i; /* work area */
77
default:
(void) fprintf(stderr, "arg.val = %d)\n", arg.val);
break;
}
i = semctl(semid, semnum, cmd, arg);
if (i == -1) {
perror("semctl: semctl failed");
exit(1);
}
(void) fprintf(stderr, "semctl: semctl returned %d\n", i);
return;
}
/*
* Display contents of commonly used pieces of the status
structure.
*/
static void
do_stat()
{
(void) fprintf(stderr, "sem_perm.uid = %d\n",
semid_ds.sem_perm.uid);
(void) fprintf(stderr, "sem_perm.gid = %d\n",
semid_ds.sem_perm.gid);
(void) fprintf(stderr, "sem_perm.cuid = %d\n",
semid_ds.sem_perm.cuid);
(void) fprintf(stderr, "sem_perm.cgid = %d\n",
semid_ds.sem_perm.cgid);
(void) fprintf(stderr, "sem_perm.mode = %#o, ",
semid_ds.sem_perm.mode);
(void) fprintf(stderr, "access permissions = %#o\n",
semid_ds.sem_perm.mode & 0777);
(void) fprintf(stderr, "sem_nsems = %d\n",
semid_ds.sem_nsems);
(void) fprintf(stderr, "sem_otime = %s", semid_ds.sem_otime ?
ctime(&semid_ds.sem_otime) : "Not Set\n");
(void) fprintf(stderr, "sem_ctime = %s",
ctime(&semid_ds.sem_ctime));
}
#include <stdio.h>
78
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
main()
{
register int i; /* work area */
int nsops; /* number of operations to do */
int semid; /* semid of semaphore set */
struct sembuf *sops; /* ptr to operations to perform */
(void) fprintf(stderr,
"All numeric input must follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
/* Loop until the invoker doesn't want to do anymore. */
while (nsops = ask(&semid, &sops)) {
/* Initialize the array of operations to be performed.*/
for (i = 0; i < nsops; i++) {
(void) fprintf(stderr,
"\nEnter values for operation %d of %d.\n",
i + 1, nsops);
(void) fprintf(stderr,
"sem_num(valid values are 0 <= sem_num < %d): ",
semid_ds.sem_nsems);
(void) scanf("%hi", &sops[i].sem_num);
(void) fprintf(stderr, "sem_op: ");
(void) scanf("%hi", &sops[i].sem_op);
(void) fprintf(stderr,
"Expected flags in sem_flg are:\n");
(void) fprintf(stderr, "\tIPC_NOWAIT =\t%#6.6o\n",
IPC_NOWAIT);
(void) fprintf(stderr, "\tSEM_UNDO =\t%#6.6o\n",
SEM_UNDO);
(void) fprintf(stderr, "sem_flg: ");
(void) scanf("%hi", &sops[i].sem_flg);
}
79
{
(void) fprintf(stderr, "\nsops[%d].sem_num = %d, ", i,
sops[i].sem_num);
(void) fprintf(stderr, "sem_op = %d, ", sops[i].sem_op);
(void) fprintf(stderr, "sem_flg = %#o\n",
sops[i].sem_flg);
}
/*
* Ask if user wants to continue.
*
* On the first call:
* Get the semid to be processed and supply it to the caller.
* On each call:
* 1. Print current semaphore values.
* 2. Ask user how many operations are to be performed on the next
* call to semop. Allocate an array of sembuf structures
* sufficient for the job and set caller-supplied pointer to
that
* array. (The array is reused on subsequent calls if it is big
* enough. If it isn't, it is freed and a larger array is
* allocated.)
*/
static
ask(semidp, sopsp)
int *semidp; /* pointer to semid (used only the first time) */
struct sembuf **sopsp;
{
static union semun arg; /* argument to semctl */
int i; /* work area */
static int nsops = 0; /* size of currently allocated
sembuf array */
static int semid = -1; /* semid supplied by user */
static struct sembuf *sops; /* pointer to allocated array */
if (semid < 0) {
/* First call; get semid from user and the current state of
the semaphore set. */
(void) fprintf(stderr,
"Enter semid of the semaphore set you want to use: ");
(void) scanf("%i", &semid);
*semidp = semid;
arg.buf = &semid_ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("semop: semctl(IPC_STAT) failed");
/* Note that if semctl fails, semid_ds remains filled
with zeros, so later test for number of semaphores will
be zero. */
(void) fprintf(stderr,
80
"Before and after values are not printed.\n");
} else {
if ((arg.array = (ushort *)malloc(
(unsigned)(sizeof(ushort) * semid_ds.sem_nsems)))
== NULL) {
(void) fprintf(stderr, error_mesg1,
semid_ds.sem_nsems);
exit(1);
}
}
}
/* Print current semaphore values. */
if (semid_ds.sem_nsems) {
(void) fprintf(stderr,
"There are %d semaphores in the set.\n",
semid_ds.sem_nsems);
if (semctl(semid, 0, GETALL, arg) == -1) {
perror("semop: semctl(GETALL) failed");
} else {
(void) fprintf(stderr, "Current semaphore values are:");
for (i = 0; i < semid_ds.sem_nsems;
(void) fprintf(stderr, " %d", arg.array[i++]));
(void) fprintf(stderr, "\n");
}
}
/* Find out how many operations are going to be done in the
next
call and allocate enough space to do it. */
(void) fprintf(stderr,
"How many semaphore operations do you want %s\n",
"on the next call to semop()?");
(void) fprintf(stderr, "Enter 0 or control-D to quit: ");
i = 0;
if (scanf("%i", &i) == EOF || i == 0)
exit(0);
if (i > nsops) {
if (nsops)
free((char *)sops);
nsops = i;
if ((sops = (struct sembuf *)malloc((unsigned)(nsops *
sizeof(struct sembuf)))) == NULL) {
(void) fprintf(stderr, error_mesg2, nsops);
exit(2);
}
}
*sopsp = sops;
return (i);
}
Exercises
Exercise 12763
Write 2 programs that will communicate both ways (i.e each process can read and write) when
run concurrently via semaphores.
81
Exercise 12764
Modify the semaphore.c program to handle synchronous semaphore communication
semaphores.
Exercise 12765
Write 3 programs that communicate together via semaphores according to the following
specifications: sem_server.c -- a program that can communicate independently (on different
semaphore tracks) with two clients programs. sem_client1.c -- a program that talks to
sem_server.c on one track. sem_client2.c -- a program that talks to sem_server.c
on another track to sem_client1.c.
Exercise 12766
Compile the programs semget.c, semctl.c and semop.c and then
• investigate and understand fully the operations of the flags (access, creation etc.
permissions) you can set interactively in the programs.
• Use the prgrams to:
• Send and receive semaphores of 3 different semaphore tracks.
• Inquire about the state of the semaphore queue with semctl.c. Add/delete a few
semaphores (using semop.c and perform the inquiry once more.
• Use semctl.c to alter a semaphore on the queue.
• Use semctl.c to delete a semaphore from the queue.
82
IPC:Shared Memory
Shared Memory is an efficeint means of passing data between programs. One program will
create a memory portion which other processes (if permitted) can access.
In the Solaris 2.x operating system, the most efficient way to implement shared memory
applications is to rely on the mmap() function and on the system's native virtual memory
facility. Solaris 2.x also supports System V shared memory, which is another way to let multiple
processes attach a segment of physical memory to their virtual address spaces. When write
access is allowed for more than one process, an outside protocol or mechanism such as a
semaphore can be used to prevent inconsistencies and collisions.
A process creates a shared memory segment using shmget()|. The original owner of a shared
memory segment can assign ownership to another user with shmctl(). It can also revoke this
assignment. Other processes with proper permission can perform various control functions on
the shared memory segment using shmctl(). Once created, a shared segment can be attached
to a process address space using shmat(). It can be detached using shmdt() (see shmop()).
The attaching process must have the appropriate permissions for shmat(). Once attached, the
process can read or write to the segment, as allowed by the permission requested in the attach
operation. A shared segment can be attached multiple times by the same process. A shared
memory segment is described by a control structure with a unique ID that points to an area of
physical memory. The identifier of the segment is called the shmid. The structure definition for
the shared memory segment control structures and prototypews can be found in <sys/shm.h>.
The key argument is a access value associated with the semaphore ID. The size argument is
the size in bytes of the requested shared memory. The shmflg argument specifies the initial
access permissions and creation control flags.
When the call succeeds, it returns the shared memory segment ID. This call is also used to get
the ID of an existing shared segment (from a process requesting sharing of some existing
memory portion).
The following code illustrates shmget():
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
...
83
...
key = ...
size = ...
shmflg) = ...
The process must have an effective shmid of owner, creator or superuser to perform this
command. The cmd argument is one of following control commands:
SHM_LOCK
-- Lock the specified shared memory segment in memory. The process must have the
effective ID of superuser to perform this command.
SHM_UNLOCK
-- Unlock the shared memory segment. The process must have the effective ID of
superuser to perform this command.
IPC_STAT
-- Return the status information contained in the control structure and place it in the buffer
pointed to by buf. The process must have read permission on the segment to perform this
command.
IPC_SET
-- Set the effective user and group identification and access permissions. The process must
have an effective ID of owner, creator or superuser to perform this command.
IPC_RMID
-- Remove the shared memory segment.
...
84
hold results */
...
shmid = ...
cmd = ...
if ((rtrn = shmctl(shmid, cmd, shmid_ds)) == -1) {
perror("shmctl: shmctl failed");
exit(1);
}
...
shmat() returns a pointer, shmaddr, to the head of the shared segment associated with a
valid shmid. shmdt() detaches the shared memory segment located at the address indicated
by shmaddr
. The following code illustrates calls to shmat() and shmdt():
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
...
p = &ap[nap++];
p->shmid = ...
p->shmaddr = ...
p->shmflg = ...
85
} else
(void) fprintf(stderr, "shmop: shmat returned %#8.8x\n",
p->shmaddr);
...
i = shmdt(addr);
if(i == -1) {
perror("shmop: shmdt failed");
} else {
(void) fprintf(stderr, "shmop: shmdt returned %d\n", i);
}
...
shm_server.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SHMSZ 27
main()
{
char c;
int shmid;
key_t key;
char *shm, *s;
/*
* We'll name our shared memory segment
* "5678".
*/
86
key = 5678;
/*
* Create the segment.
*/
if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
/*
* Now we attach the segment to our data space.
*/
if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
perror("shmat");
exit(1);
}
/*
* Now put some things into the memory for the
* other process to read.
*/
s = shm;
/*
* Finally, we wait until the other process
* changes the first character of our memory
* to '*', indicating that it has read what
* we put there.
*/
while (*shm != '*')
sleep(1);
exit(0);
}
shm_client.c
/*
* shm-client - client program to demonstrate shared memory.
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SHMSZ 27
main()
{
int shmid;
key_t key;
char *shm, *s;
87
/*
* We need to get the segment named
* "5678", created by the server.
*/
key = 5678;
/*
* Locate the segment.
*/
if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
perror("shmget");
exit(1);
}
/*
* Now we attach the segment to our data space.
*/
if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
perror("shmat");
exit(1);
}
/*
* Now read what the server put in the memory.
*/
for (s = shm; *s != NULL; s++)
putchar(*s);
putchar('\n');
/*
* Finally, change the first character of the
* segment to '*', indicating we have read
* the segment.
*/
*shm = '*';
exit(0);
}
Mapped memory
In a system with fixed memory (non-virtual), the address space of a process occupies and is
limited to a portion of the system's main memory. In Solaris 2.x virtual memory the actual
88
address space of a process occupies a file in the swap partition of disk storage (the file is called
the backing store). Pages of main memory buffer the active (or recently active) portions of the
process address space to provide code for the CPU(s) to execute and data for the program to
process.
A page of address space is loaded when an address that is not currently in memory is accessed by
a CPU, causing a page fault. Since execution cannot continue until the page fault is resolved by
reading the referenced address segment into memory, the process sleeps until the page has been
read. The most obvious difference between the two memory systems for the application
developer is that virtual memory lets applications occupy much larger address spaces. Less
obvious advantages of virtual memory are much simpler and more efficient file I/O and very
efficient sharing of memory between processes.
Coherence
Whether to share memory or to share data contained in the file, when multiple process map a file
simultaneously there may be problems with simultaneous access to data elements. Such
89
processes can cooperate through any of the synchronization mechanisms provided in Solaris 2.x.
Because they are very light weight, the most efficient synchronization mechanisms in Solaris 2.x
are the threads library ones.
The mapping established by mmap() replaces any previous mappings for specified address
range. The flags MAP_SHARED and MAP_PRIVATE specify the mapping type, and one of
them must be specified. MAP_SHARED specifies that writes modify the mapped object. No
further operations on the object are needed to make the change. MAP_PRIVATE specifies that
an initial write to the mapped area creates a copy of the page and all writes reference the copy.
Only modified pages are copied.
A mapping type is retained across a fork(). The file descriptor used in a mmap call need not
be kept open after the mapping is established. If it is closed, the mapping remains until the
mapping is undone by munmap() or be replacing in with a new mapping. If a mapped file is
shortened by a call to truncate, an access to the area of the file that no longer exists causes a
SIGBUS signal.
The following code fragment demonstrates a use of this to create a block of scratch storage in a
program, at an address that the system chooses.:
int fd;
caddr_t result;
if ((fd = open("/dev/zero", O_RDWR)) == -1)
return ((caddr_t)-1);
90
superuser. The system lets only a configuration dependent limit of pages be locked in memory.
The call to mlock fails if this limit is exceeded.
int munlock(caddr_t addr, size_t len) releases the locks on physical pages. If
multiple mlock() calls are made on an address range of a single mapping, a single munlock
call is release the locks. However, if different mappings to the same pages are mlocked, the
pages are not unlocked until the locks on all the mappings are released. Locks are also released
when a mapping is removed, either through being replaced with an mmap operation or removed
with munmap. A lock is transferred between pages on the ``copy-on-write' event associated with
a MAP_PRIVATE mapping, thus locks on an address range that includes MAP_PRIVATE
mappings will be retained transparently along with the copy-on-write redirection (see mmap
above for a discussion of this redirection)
int mlockall(int flags) and int munlockall(void) are similar to mlock()
and munlock(), but they operate on entire address spaces. mlockall() sets locks on all
pages in the address space and munlockall() removes all locks on all pages in the address
space, whether established by mlock or mlockall.
int msync(caddr_t addr, size_t len, int flags) causes all modified pages
in the specified address range to be flushed to the objects mapped by those addresses. It is
similar to fsync() for files.
long sysconf(int name) returns the system dependent size of a memory page. For
portability, applications should not embed any constants specifying the size of a page. Note that
it is not unusual for page sizes to vary even among implementations of the same instruction set.
int mprotect(caddr_t addr, size_t len, int prot) assigns the specified
protection to all pages in the specified address range. The protection cannot exceed the
permissions allowed on the underlying object.
int brk(void *endds) and void *sbrk(int incr) are called to add storage to the
data segment of a process. A process can manipulate this area by calling brk() and sbrk().
brk() sets the system idea of the lowest data segment location not used by the caller to addr
(rounded up to the next multiple of the system page size). sbrk() adds incr bytes to the caller
data space and returns a pointer to the start of the new data area.
91
*
* This is a simple exerciser of the shmget() function. It
prompts
* for the arguments, makes the call, and reports the results.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
main()
{
key_t key; /* key to be passed to shmget() */
int shmflg; /* shmflg to be passed to shmget() */
int shmid; /* return value from shmget() */
int size; /* size to be passed to shmget() */
(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
92
exit(1);
} else {
(void) fprintf(stderr,
"shmget: shmget returned %d\n", shmid);
exit(0);
}
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
static void do_shmctl();
extern void exit();
extern void perror();
main()
{
int cmd; /* command code for shmctl() */
int shmid; /* segment ID */
struct shmid_ds shmid_ds; /* shared memory data structure to
hold results */
(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
93
(void) fprintf(stderr, "\tSHM_UNLOCK =\t%d\n", SHM_UNLOCK);
(void) fprintf(stderr, "Enter the desired cmd value: ");
(void) scanf("%i", &cmd);
switch (cmd) {
case IPC_STAT:
/* Get shared memory segment status. */
break;
case IPC_SET:
/* Set owner UID and GID and permissions. */
/* Get and print current values. */
do_shmctl(shmid, IPC_STAT, &shmid_ds);
/* Set UID, GID, and permissions to be loaded. */
(void) fprintf(stderr, "\nEnter shm_perm.uid: ");
(void) scanf("%hi", &shmid_ds.shm_perm.uid);
(void) fprintf(stderr, "Enter shm_perm.gid: ");
(void) scanf("%hi", &shmid_ds.shm_perm.gid);
(void) fprintf(stderr,
"Note: Keep read permission for yourself.\n");
(void) fprintf(stderr, "Enter shm_perm.mode: ");
(void) scanf("%hi", &shmid_ds.shm_perm.mode);
break;
case IPC_RMID:
/* Remove the segment when the last attach point is
detached. */
break;
case SHM_LOCK:
/* Lock the shared memory segment. */
break;
case SHM_UNLOCK:
/* Unlock the shared memory segment. */
break;
default:
/* Unknown command will be passed to shmctl. */
break;
}
do_shmctl(shmid, cmd, &shmid_ds);
exit(0);
}
/*
* Display the arguments being passed to shmctl(), call shmctl(),
* and report the results. If shmctl() fails, do not return; this
* example doesn't deal with errors, it just reports them.
*/
static void
do_shmctl(shmid, cmd, buf)
int shmid, /* attach point */
cmd; /* command code */
struct shmid_ds *buf; /* pointer to shared memory data structure */
{
register int rtrn; /* hold area */
94
buf->shm_perm.uid);
(void) fprintf(stderr, "\tbuf->shm_perm.gid == %d\n",
buf->shm_perm.gid);
(void) fprintf(stderr, "\tbuf->shm_perm.mode == %#o\n",
buf->shm_perm.mode);
}
if ((rtrn = shmctl(shmid, cmd, buf)) == -1) {
perror("shmctl: shmctl failed");
exit(1);
} else {
(void) fprintf(stderr,
"shmctl: shmctl returned %d\n", rtrn);
}
if (cmd != IPC_STAT && cmd != IPC_SET)
return;
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
95
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
static ask();
static void catcher();
extern void exit();
static good_addr();
extern void perror();
extern char *shmat();
main()
{
register int action; /* action to be performed */
char *addr; /* address work area */
register int i; /* work area */
register struct state *p; /* ptr to current state entry */
void (*savefunc)(); /* SIGSEGV state hold area */
(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
while (action = ask()) {
if (nap) {
(void) fprintf(stderr,
"\nCurrently attached segment(s):\n");
(void) fprintf(stderr, " shmid address\n");
(void) fprintf(stderr, "------ ----------\n");
p = &ap[nap];
while (p-- != ap) {
(void) fprintf(stderr, "%6d", p->shmid);
(void) fprintf(stderr, "%#11x", p->shmaddr);
(void) fprintf(stderr, " Read%s\n",
(p->shmflg & SHM_RDONLY) ?
"-Only" : "/Write");
}
} else
(void) fprintf(stderr,
"\nNo segments are currently attached.\n");
switch (action) {
case 1: /* Shmat requested. */
/* Verify that there is space for another attach. */
if (nap == MAXnap) {
96
(void) fprintf(stderr, "%s %d %s\n",
"This simple example will only allow",
MAXnap, "attached segments.");
break;
}
p = &ap[nap++];
/* Get the arguments, make the call, report the
results, and update the current state array. */
(void) fprintf(stderr,
"Enter shmid of segment to attach: ");
(void) scanf("%i", &p->shmid);
(void) fprintf(stderr,
"shmop: Calling shmat(%d, %#x, %#o)\n",
p->shmid, p->shmaddr, p->shmflg);
p->shmaddr = shmat(p->shmid, p->shmaddr, p->shmflg);
if(p->shmaddr == (char *)-1) {
perror("shmop: shmat failed");
nap--;
} else {
(void) fprintf(stderr,
"shmop: shmat returned %#8.8x\n",
p->shmaddr);
}
break;
i = shmdt(addr);
if(i == -1) {
perror("shmop: shmdt failed");
} else {
(void) fprintf(stderr,
"shmop: shmdt returned %d\n", i);
for (p = ap, i = nap; i--; p++) {
if (p->shmaddr == addr)
*p = ap[--nap];
}
}
break;
case 3: /* Read from segment requested. */
if (nap == 0)
97
break;
if (good_addr(addr))
(void) fprintf(stderr, "String @ %#x is `%s'\n",
addr, addr);
break;
if (setjmp(segvbuf)) {
(void) fprintf(stderr, "shmop: %s: %s\n",
"SIGSEGV signal caught",
"Write aborted.");
} else {
if (good_addr(addr)) {
(void) fflush(stdin);
(void) fprintf(stderr, "%s %s %#x:\n",
"Enter one line to be copied",
"to shared segment attached @",
addr);
(void) gets(addr);
}
}
(void) fflush(stdin);
98
(void) fprintf(stderr, "\t 1 = shmat\n");
(void) fprintf(stderr, "\t 2 = shmdt\n");
(void) fprintf(stderr, "\t 3 = read from segment\n");
(void) fprintf(stderr, "\t 4 = write to segment\n");
(void) fprintf(stderr,
"Enter the number corresponding to your choice: ");
Exercises
Exercise 12771
Write 2 programs that will communicate via shared memory and semaphores. Data will be
exchanged via memory and semaphores will be used to synchronise and notify each process
when operations such as memory loaded and memory read have been performed.
Exercise 12772
Compile the programs shmget.c, shmctl.c and shmop.c and then
• investigate and understand fully the operations of the flags (access, creation etc.
99
permissions) you can set interactively in the programs.
• Use the prgrams to:
• Exchange data between two processe running as shmop.c.
• Inquire about the state of shared memory with shmctl.c.
• Use semctl.c to lock a shared memory segment.
• Use semctl.c to delete a shared memory segment.
Exercise 12773
Write 2 programs that will communicate via mapped memory.
100
IPC:Sockets
Sockets provide point-to-point, two-way communication between two processes. Sockets are
very versatile and are a basic component of interprocess and intersystem communication. A
socket is an endpoint of communication to which a name can be bound. It has a type and one or
more associated processes.
Sockets exist in communication domains. A socket domain is an abstraction that provides an
addressing structure and a set of protocols. Sockets connect only with sockets in the same
domain. Twenty three socket domains are identified (see <sys/socket.h>), of which only
the UNIX and Internet domains are normally used Solaris 2.x Sockets can be used to
communicate between processes on a single system, like other forms of IPC.
The UNIX domain provides a socket address space on a single system. UNIX domain sockets
are named with UNIX paths. Sockets can also be used to communicate between processes on
different systems. The socket address space between connected systems is called the Internet
domain.
Internet domain communication uses the TCP/IP internet protocol suite.
Socket types define the communication properties visible to the application. Processes
communicate only between sockets of the same type. There are five types of socket.
A stream socket
-- provides two-way, sequenced, reliable, and unduplicated flow of data with no record
boundaries. A stream operates much like a telephone conversation. The socket type is
SOCK_STREAM, which, in the Internet domain, uses Transmission Control Protocol
(TCP).
A datagram socket
-- supports a two-way flow of messages. A on a datagram socket may receive messages in
a different order from the sequence in which the messages were sent. Record boundaries in
the data are preserved. Datagram sockets operate much like passing letters back and forth
in the mail. The socket type is SOCK_DGRAM, which, in the Internet domain, uses User
Datagram Protocol (UDP).
A sequential packet socket
-- provides a two-way, sequenced, reliable, connection, for datagrams of a fixed maximum
length. The socket type is SOCK_SEQPACKET. No protocol for this type has been
implemented for any protocol family.
A raw socket
provides access to the underlying communication protocols.
These sockets are usually datagram oriented, but their exact characteristics depend on the
interface provided by the protocol.
101
defaults to a protocol that supports the specified socket type. The socket handle (a descriptor) is
returned. A remote process has no way to identify a socket until an address is bound to it.
Communicating processes connect through addresses. In the UNIX domain, a connection is
usually composed of one or two path names. In the Internet domain, a connection is composed
of local and remote addresses and local and remote ports. In most domains, connections must be
unique.
int bind(int s, const struct sockaddr *name, int namelen) is called to
bind a path or internet address to a socket. There are three different ways to call bind(),
depending on the domain of the socket.
• For UNIX domain sockets with paths containing 14, or fewer characters, you can:
#include <sys/socket.h>
...
bind (sd, (struct sockaddr *) &addr, length);
In the UNIX domain, binding a name creates a named socket in the file system. Use unlink()
or rm () to remove the socket.
If the client's socket is unbound at the time of the connect call, the system automatically selects
and binds a name to the socket. For a SOCK_STREAM socket, the server calls accept(3N) to
102
complete the connection.
int accept(int s, struct sockaddr *addr, int *addrlen) returns a new
socket descriptor which is valid only for the particular connection. A server can have multiple
SOCK_STREAM connections active at one time.
Datagram sockets
A datagram socket does not require that a connection be established. Each message carries the
destination address. If a particular local address is needed, a call to bind() must precede any
data transfer. Data is sent through calls to sendto() or sendmsg(). The sendto() call is
like a send() call with the destination address also specified. To receive datagram socket
messages, call recvfrom() or recvmsg(). While recv() requires one buffer for the
arriving data, recvfrom() requires two buffers, one for the incoming message and another to
receive the source address.
Datagram sockets can also use connect() to connect the socket to a specified destination
socket. When this is done, send() and recv() are used to send and receive data.
accept() and listen() are not used with datagram sockets.
Socket Options
Sockets have a number of options that can be fetched with getsockopt() and set with
setsockopt(). These functions can be used at the native socket level (level =
103
SOL_SOCKET), in which case the socket option name must be specified. To manipulate options
at any other level the protocol number of the desired protocol controlling the option of interest
must be specified (see getprotoent() in getprotobyname()).
Example Socket
Programs:socket_server.c,socket_c
lient
These two programs show how you can establish a socket connection using the above functions.
socket_server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
/*
* Strings we send to the client.
*/
char *strs[NSTRS] = {
"This is the first string from the server.\n",
"This is the second string from the server.\n",
"This is the third string from the server.\n"
};
main()
{
char c;
FILE *fp;
int fromlen;
register int i, s, ns, len;
struct sockaddr_un saun, fsaun;
/*
* Get a socket to work with. This socket will
* be in the UNIX domain, and will be a
* stream socket.
*/
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("server: socket");
exit(1);
}
/*
* Create the address we will be binding to.
*/
saun.sun_family = AF_UNIX;
strcpy(saun.sun_path, ADDRESS);
104
/*
* Try to bind the address to the socket. We
* unlink the name first so that the bind won't
* fail.
*
* The third argument indicates the "length" of
* the structure, not just the length of the
* socket name.
*/
unlink(ADDRESS);
len = sizeof(saun.sun_family) + strlen(saun.sun_path);
/*
* Listen on the socket.
*/
if (listen(s, 5) < 0) {
perror("server: listen");
exit(1);
}
/*
* Accept connections. When we accept one, ns
* will be connected to the client. fsaun will
* contain the address of the client.
*/
if ((ns = accept(s, &fsaun, &fromlen)) < 0) {
perror("server: accept");
exit(1);
}
/*
* We'll use stdio for reading the socket.
*/
fp = fdopen(ns, "r");
/*
* First we send some strings to the client.
*/
for (i = 0; i < NSTRS; i++)
send(ns, strs[i], strlen(strs[i]), 0);
/*
* Then we read some strings from the client and
* print them out.
*/
for (i = 0; i < NSTRS; i++) {
while ((c = fgetc(fp)) != EOF) {
putchar(c);
if (c == '\n')
break;
}
105
}
/*
* We can simply use close() to terminate the
* connection, since we're done with both sides.
*/
close(s);
exit(0);
}
socket_client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
/*
* Strings we send to the server.
*/
char *strs[NSTRS] = {
"This is the first string from the client.\n",
"This is the second string from the client.\n",
"This is the third string from the client.\n"
};
main()
{
char c;
FILE *fp;
register int i, s, len;
struct sockaddr_un saun;
/*
* Get a socket to work with. This socket will
* be in the UNIX domain, and will be a
* stream socket.
*/
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("client: socket");
exit(1);
}
/*
* Create the address we will be connecting to.
*/
saun.sun_family = AF_UNIX;
strcpy(saun.sun_path, ADDRESS);
/*
* Try to connect to the address. For this to
* succeed, the server must already have bound
* this address, and must have issued a listen()
106
* request.
*
* The third argument indicates the "length" of
* the structure, not just the length of the
* socket name.
*/
len = sizeof(saun.sun_family) + strlen(saun.sun_path);
/*
* We'll use stdio for reading
* the socket.
*/
fp = fdopen(s, "r");
/*
* First we read some strings from the server
* and print them out.
*/
for (i = 0; i < NSTRS; i++) {
while ((c = fgetc(fp)) != EOF) {
putchar(c);
if (c == '\n')
break;
}
}
/*
* Now we send some strings to the server.
*/
for (i = 0; i < NSTRS; i++)
send(s, strs[i], strlen(strs[i]), 0);
/*
* We can simply use close() to terminate the
* connection, since we're done with both sides.
*/
close(s);
exit(0);
}
Exercises
Exercise 12776
Configure the above socket_server.c and socket_client.c programs for you system
and compile and run them. You will need to set up socket ADDRESS definition.
107