C SC 340 Lecture 3: Processes
[ previous
| schedule
| next ]
What is a process?
- a program in execution
- major components of a process include
- its program code (text)
- its global variables (data)
- its temporary data (stack)
- its dynamically-allocated data (heap)
- its program counter and register values
- multiple processes exist simultaneously. In Windows do Ctrl-Alt-Del then
click Task Manager then Processes; in Linux type ps -e (or ps -el for long format)
- multiple processes can be associated with the same program
- each process operates in a separate space from other processes
Each process has identity
Every process is given an identifier when created, typically an integer value known as
its Process I.D. (PID).
Each process has state
- Recall in OOP that each object has a state, which is the collection
of its fields' values
- Processes have state too; it changes over time
- new, initial state while being created
- ready, "eligible for execution"
- running, using the CPU
- waiting, waiting for an event (not running, not ready)
- terminated, finished with CPU but still exists
- process is represented by data structure called process control block, which contains info such as:
- process state
- saved program counter
- saved register values
- list of files and devices in use
- memory management info
- process management info
- thread info
- OS maintains all necessary info about the process in its PCB.
Scheduling and running Processes
- concurrent processing: multiple processes exist at the same time
but only one may be in running state - single CPU
- parallel (simultaneous) processing: multiple processes are running at
the same time - multiple CPUs
- scheduling is important policy whether concurrent or parallel processing
- PCBs spend their existence moving from queue to queue.
- "ready" queue if in ready state
- "device" queue if waiting for I/O
- etc
- process schedulers are kernel code to maintain queues
- a long-term scheduler runs occasionally to keep right mix of processes in memory
- a short-term CPU scheduler runs frequently to keep right mix of processes on CPU
- the corresponding mechanism is context switching
- context switching involves switching "current process" pointer from one PCB to another
and saving/loading register values
Process creation
- A process can be created only by another running process
- The creator is the parent process, the created is its child process
- A parent can create many children; a child has but one parent (whose PID is stored in its PCB)
- child may be constrained to using its parent's resources, may not
- parent can pass initialization data and possibly resources to child upon creation
- parent execute concurrently with its children and/or await child termination
- child process may be created as clone of parent
- details on Unix process creation:
- parent creates child by calling fork system call
- child is clone of parent (duplicate process space and most of PCB)
- by default both continue from same program counter
- in reality, child is given different code to execute:
- return value of fork is 0 for child, child's PID for parent. This is
used as condition of if-then-else
- one or the other can then overwrite its own code space with executable code
from a file by calling exec system call
- the newly-loaded code subsequently runs under the same PID
- the term exec above refers to a group of related system calls
- to see Linux process list, do command: ps -e (or ps -el for long format)
Process termination
- Unix process terminates by calling exit system call. argument
value passed to parent if waiting
- it may also be terminated externally (by parent or kernel)
- Parent is notified when child terminates, and parent resumes at statement following wait
(if it was waiting)
- Some OSs do not allow parent to terminate until all children have; Unix
allows this but requires child to be adopted by special kernel init
process (PID 1).
Simple non-robust Unix example
#include <stdio.h>
#include <unistd.h>
int main() {
if (fork()) {
printf("I am parent, my PID is %d.\n",getpid());
wait();
printf("My child is terminated, I am leaving too.\n");
} else {
printf("I am child, my PID is %d.\n",getpid());
}
exit(0);
}
Sample output:
I am parent, my PID is 1265.
I am child, my PID is 1266.
My child is terminated, I am leaving too.
Concurrent Cooperating processes
With concurrency, multiple processes exist at the same time. If they communicate with each
other or depend on each other, they are called cooperating processes.
Such communication of data or information is called interprocess communication (IPC). IPC
mechanisms fall into two categories: shared memory and message passing
The classic example of cooperating processes is the producer-consumer
problem.
This is nicely illustrated by the classic I Love Lucy chocolate factory scene.
- One process, the producer, produces information.
- Another process, the consumer, consumes that information.
- They do not run in lockstep because this is too
much a constraint.
- They share a limited-size buffer to hold the information.
- The only producer constraint: if buffer is full, has to wait until a slot free
- The only consumer constraint: if buffer is empty, has to wait until a slot fills
The producer and consumer cooperate through the shared buffer. There are variations on the problem
for unbounded (infinite) buffer, bounded (finite) buffer, and zero capacity buffer.
Message-passing IPC facilities
are simple in concept: send(toID, message) and receive(fromID,
message) are the only calls required. They are varied and possibly complex in
implementation.
Processes communicating via IPC may communicate directly, using their
partner's process IDs as the toID and fromID. Alternatively,
they may communicate indirectly, using a port or mailbox
as the toID and fromID. In this case, both are the same ID
and it must be accessible to both parties. Ports and mailboxes are akin to buffers
because they are an intermediary. Communication is not limited to two parties:
anyone who has access to the same shared ID can participate. Kind of like IM
or chat.
The send and receive operations can be implemented differently
to achieve different degrees of synchronization between sender and
receiver.
- send can be implemented using blocking,
which means the sender is blocked until the message is received by the receiver
or mailbox.
- receive can be implemented using blocking, which means
the receiver blocks until a message is sent.
- If there is no buffering between them, they are strictly synchronized.
- If there is an "infinite" buffer, the sender will never block but the receiver
still blocks on empty buffer.
- If you desire both sender and receiver to be non-blocking, then you must
provide an infinite buffer and have receiver return a null if buffer is empty.
Unix support for producer/consumer: pipe
The Unix pipe() system call creates a bounded buffer (size is system-dependent)
through which two processes may interact, one as producer and the other as consumer.
The producer will block if the pipe is full and the consumer will block if the
pipe is empty.
To make the pipe shareable, it must be created before a fork(), then
after the fork the parent and child processes both have access to the pipe and
communicate by reading/writing to it.
Here is an example showing the setup of a pipe then after the fork the parent
sends the child some characters through the pipe and the child reads and prints
them. The pipe-related code is shown in boldface.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFFER_CAPACITY 6
char *packets[] = { "Hello there" , " from me." };
int main() {
int channels[2], readChannel, writeChannel, pipeCreateFailed;
pid_t processID;
pipeCreateFailed = pipe(channels);
if (pipeCreateFailed) {
printf("pipe failed.\n");
exit(-1);
} else {
readChannel = channels[0];
writeChannel = channels[1];
}
processID = fork();
if (processID < 0) { /* fork failure */
printf("fork failed.\n");
exit(-1);
} else if (processID > 0) { /* parent code */
int numPackets, i;
close(readChannel);
numPackets = sizeof(packets)/sizeof(char *);
for (i=0; i<numPackets; i++) {
write(writeChannel, packets[i], strlen(packets[i]));
}
close(writeChannel);
wait();
printf("parent terminates\n");
} else { /* child code */
char buffer[BUFFER_CAPACITY+1];
int bytes;
close(writeChannel);
while ( (bytes = read(readChannel, buffer, BUFFER_CAPACITY)) != 0 ) {
buffer[bytes] = '\0';
printf("%d chars :%s: received by child\n", bytes, buffer);
}
close(readChannel);
printf("child terminates\n");
}
exit(0);
}
This code requires a little explanation:
- the parameter to pipe() is an array of two int.
- pipe() creates a file descriptor ("channel") for reading into element 0.
- pipe() creates a file descriptor ("channel") for writing into element 1.
- the process doing the writing (the parent in this case) must first close its read
file descriptor.
- the process doing the reading (the child in this case) must first close its write
file descriptor.
- writing to the pipe is done here using the write() system call
(higher level functions can also be used).
- its first parameter is the file descriptor for writing
- its second parameter is the address of the data to be written
- its third parameter is the number of bytes to write
- its return value is the number of bytes written
- reading from the pipe is done here using the read() system call
(higher level functions can also be used).
- its first parameter is the file descriptor for reading
- its second parameter is the address of the buffer to hold the data read
- its third parameter is the number of bytes to read
- its return value is the number of bytes actually read (if 0, writer has closed its pipe!)
Internet Client-Server communication: sockets
Sockets are a low-level IPC (InterProcess Communication) mechanism through which client and server processes
may communicate with each other across the Internet. Both may reside on the same machine or
on different machines.
This is an asymmetric arrangement. Before communication can occur, the server process
must be running and listening for client requests on a specific port. In order to make
a request, the client must first know the IP address and port number associated with
the server process. It makes the request, rendezvous occurs and communication proceeds according
to the protocol defined by their respective programs. The server continues to run after finished
with the client.
Here is a simple sockets-based application for an upper-case server. Client sends a string and
the server converts the string to all upper case then sends the result back. The example code
is shown in Java because it is somewhat cleaner than the equivalent C code. All input and
output is done through stream objects that are attached to the socket.
The Client
import java.io.*;
import java.net.*;
class TCPClient {
static final String SERVER = "PeteSanderson.otterbein.edu";
static final int PORT = 6789;
public static void main(String argv[]) throws Exception {
String sentence;
String modifiedSentence;
BufferedReader inFromUser =
new BufferedReader(new InputStreamReader(System.in));
Socket clientSocket = new Socket(SERVER, PORT);
DataOutputStream outToServer =
new DataOutputStream(clientSocket.getOutputStream());
BufferedReader inFromServer =
new BufferedReader(new
InputStreamReader(clientSocket.getInputStream()));
sentence = inFromUser.readLine();
outToServer.writeBytes(sentence + '\n');
modifiedSentence = inFromServer.readLine();
System.out.println("FROM SERVER: " + modifiedSentence);
clientSocket.close();
}
}
The Server
import java.io.*;
import java.net.*;
class TCPServer {
static final int PORT = 6789;
public static void main(String argv[]) throws Exception {
String clientSentence;
String capitalizedSentence;
ServerSocket welcomeSocket = new ServerSocket(PORT);
while(true) {
Socket connectionSocket = welcomeSocket.accept();
BufferedReader inFromClient =
new BufferedReader(new
InputStreamReader(connectionSocket.getInputStream()));
DataOutputStream outToClient =
new DataOutputStream(connectionSocket.getOutputStream());
clientSentence = inFromClient.readLine();
capitalizedSentence = clientSentence.toUpperCase() + '\n';
outToClient.writeBytes(capitalizedSentence);
}
}
}
[ C
SC 340 | Peter
Sanderson | Math Sciences server
| Math Sciences home page
| Otterbein ]
Last updated:
Peter Sanderson (PSanderson@otterbein.edu)