Showing posts with label IPC and FILE I/O. Show all posts
Showing posts with label IPC and FILE I/O. Show all posts

Linux kernel and classic Unix systems

 Main differences exist between the Linux kernel and classic Unix systems:


1.Linux supports the dynamic loading of kernel modules.Although the Linux kernel is monolithic, it can dynamically load and unload kernel code on demand.

2.Linux has symmetrical multiprocessor (SMP) support.Although most commercial variants of Unix now support SMP, most traditional Unix implementations did not.

3.The Linux kernel is preemptive. Unlike traditional Unix variants, the Linux kernel can preempt a task even as it executes in the kernel. Of the other commercial Unix implementations, Solaris and IRIX have preemptive kernels, but most Unix kernels are not preemptive.

4. Linux takes an interesting approach to thread support: It does not differentiate between threads and normal processes.To the kernel, all processes are the same— some just happen to share resources.

5. Linux provides an object-oriented device model with device classes, hot-pluggable events, and a user-space device filesystem (sysfs).

6.Linux ignores some common Unix features that the kernel developers consider poorly designed, such as STREAMS, or standards that are impossible to cleanly implement.

7.Linux is free in every sense of the word.The feature set Linux implements is the result of the freedom of Linux’s open development model. If a feature is without merit or poorly thought out, Linux developers are under no obligation to implement it.To the contrary, Linux has adopted an elitist attitude toward changes: Modifications must solve a specific real-world problem, derive from a clean design, and have a solid implementation. Consequently, features of some other modern Unix variants that are more marketing bullet or one-off requests, such as pageable kernel memory, have received no consideration.


Despite these differences, however, Linux remains an operating system with a strong Unix heritage.

More About Sockets...... protocols and syntax...!!!!

Socket Protocols

Where the underlying transport mechanism allows for more than one protocol to provide the requested socket type, you can select a specific protocol for a socket.

Creating a Socket
 
The socket system call creates a socket and returns a descriptor that can be used for accessing the socket.

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

The socket created is one end point of a communication channel. The domain parameter specifies the address family, the type parameter specifies the type of communication to be used with this socket, and protocol specifies the protocol to be employed.

Domains include the following:

AF_UNIX UNIX internal (file system sockets)
AF_INET ARPA Internet protocols (UNIX network sockets)
AF_ISO ISO standard protocols
AF_NS Xerox Network Systems protocols
AF_IPX Novell IPX protocol
AF_APPLETALK Appletalk DDS

The most common socket domains are AF_UNIX, which is used for local sockets implemented via the UNIX and Linux file systems, and AF_INET, which is used for UNIX network sockets. The AF_INET sockets may be used by programs communicating across a TCP/IP network including the Internet. The Windows Winsock interface also provides access to this socket domain.
The socket parameter type specifies the communication characteristics to be used for the new socket. Possible values include SOCK_STREAM and SOCK_DGRAM.
SOCK_STREAM is a sequenced, reliable, connection-based two-way byte stream. For an AF_INET domain socket, this is provided by default by a TCP connection that is established between the two end points of the stream socket when it’s connected. Data may be passed in both directions along the socket connection. The TCP protocols include facilities to fragment and reassemble long messages and to retransmit any parts that may be lost in the network.
SOCK_DGRAM is a datagram service. You can use this socket to send messages of a fixed (usually small) maximum size, but there’s no guarantee that the message will be delivered or that messages won’t be reordered in the network. For AF_INET sockets, this type of communication is provided by UDP datagrams.


The protocol used for communication is usually determined by the socket type and domain. There is normally no choice. The protocol parameter is used where there is a choice. 0 selects the default protocol, which we’ll use in all our examples.
The socket system call returns a descriptor that is in many ways similar to a low-level file descriptor. When the socket has been connected to another end-point socket, you may use the read and write system calls with the descriptor to send and receive data on the socket. The close system call is used to end a socket connection.

Socket Addresses

Each socket domain requires its own address format. For an AF_UNIX socket, the address is described by a structure, sockaddr_un, defined in the sys/un.h include file.

struct sockaddr_un {
sa_family_t
sun_family;
char
sun_path[];
}
;
/* AF_UNIX */
/* pathname */


So that addresses of different types may be passed to the socket-handling system calls, each address format is described by a similar structure that begins with a field (in this case, sun_family) that specifies the address type (the socket domain). In the AF_UNIX domain, the address is specified by a filename in the sun_path field of the structure.
On current Linux systems, the type sa_family_t, defined by X/Open as being declared in sys/un.h, is taken to be a short. Also, the pathname specified in sun_path is limited in size (Linux specifies 108 characters; others may use a manifest constant such as UNIX_MAX_PATH). Because address structures
may vary in size, many socket calls require or provide as an output a length to be used for copying the particular address structure.
In the AF_INET domain, the address is specified using a structure called sockaddr_in, defined in netinet/in.h, which contains at least these members:

struct sockaddr_in {
short int
unsigned short int
struct in_addr
};
sin_family;
sin_port;
sin_addr;

/* AF_INET */
/* Port number */
/* Internet address */

The IP address structure, in_addr, is defined as follows:

struct in_addr {
unsigned long int
};
s_addr;

The four bytes of an IP address constitute a single 32-bit value. An AF_INET socket is fully described by its domain, IP address, and port number. From an application’s point of view, all sockets act like file descriptors and are addressed by a unique integer value.

Naming a Socket
 
To make a socket (as created by a call to socket) available for use by other processes, a server program needs to give the socket a name. Thus, AF_UNIX sockets are associated with a file system pathname, as you saw in the server1 example. AF_INET sockets are associated with an IP port  number.

#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);

The bind system call assigns the address specified in the parameter, address, to the unnamed socket associated with the file descriptor socket. The length of the address structure is passed as address_len. The length and format of the address depend on the address family. A particular address structure pointer will need to be cast to the generic address type (struct sockaddr *) in the call to bind. On successful completion, bind returns 0. If it fails, it returns -1 and sets errno to one of the following.

EBADF The file descriptor is invalid.
ENOTSOCK The file descriptor doesn’t refer to a socket.
EINVAL The file descriptor refers to an already-named socket.
EADDRNOTAVAIL The address is unavailable.
EADDRINUSE The address has a socket bound to it already. There are some more values for AF_UNIX sockets:
EACCESS Can’t create the file system name due to permissions.
ENOTDIR, ENAMETOOLONG Indicates a poor choice of filename.
 
Creating a Socket Queue

To accept incoming connections on a socket, a server program must create a queue to store pending requests. It does this using the listen system call.

#include <sys/socket.h>
int listen(int socket, int backlog);

A Linux system may limit the maximum number of pending connections that may be held in a queue. Subject to this maximum, listen sets the queue length to backlog. Incoming connections up to this queue length are held pending on the socket; further connections will be refused and the client’s connection will fail. This mechanism is provided by listen to allow incoming connections to be held pending while a server program is busy dealing with a previous client. A value of 5 for backlog is very common. The listen function will return 0 on success or -1 on error. Errors include EBADF, EINVAL, and ENOTSOCK, as for the bind system call.

Accepting Connections
 
Once a server program has created and named a socket, it can wait for connections to be made to the socket by using the accept system call.

#include <sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t *address_len);

The accept system call returns when a client program attempts to connect to the socket specified by the parameter socket. The client is the first pending connection from that socket’s queue. The accept function creates a new socket to communicate with the client and returns its descriptor. The new socket will have the same type as the server listen socket. The socket must have previously been named by a call to bind and had a connection queue allocated by listen. The address of the calling client will be placed in the sockaddr structure pointed to by address. A null pointer may be used here if the client address isn’t of interest.
The address_len parameter specifies the length of the client structure. If the client address is longer than this value, it will be truncated. Before calling accept, address_len must be set to the expected address length. On return, address_len will be set to the actual length of the calling client’s address
structure.
If there are no connections pending on the socket’s queue, accept will block (so that the program won’t continue) until a client makes a connection. You  may change this behavior by using the O_NONBLOCK flag on the socket file descriptor, using the fcntl function like this:

int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK|flags);
The accept function returns a new socket file descriptor when there is a client connection pending or -1 on error. Possible errors are similar to those for bind and listen, with the addition of EWOULDBLOCK, where O_NONBLOCK has been specified and there are no pending connections. The error EINTR will occur if the process is interrupted while blocked in accept.

Requesting Connections

Client programs connect to servers by establishing a connection between an unnamed socket and the server listen socket. They do this by calling connect.

#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, size_t address_len);

The socket specified by the parameter socket is connected to the server socket specified by the parameter address, which is of length address_len. The socket must be a valid file descriptor obtained by a call to socket.

If it succeeds, connect returns 0, and -1 is returned on error. Possible errorsthis time include the following:
EBADF An invalid file descriptor was passed in socket.
EALREADY A connection is already in progress for this socket.
ETIMEDOUT A connection timeout has occurred.
ECONNREFUSED The requested connection was refused by the server.
If the connection can’t be set up immediately, connect will block for an unspecified timeout period.

Once this timeout has expired, the connection will be aborted and connect will fail. However, if the call to connect is interrupted by a signal that is handled, the connect call will fail (with errno set to EINTR), but the connection attempt won’t be aborted but rather will be set up asynchronously. As with accept, the blocking nature of connect may be altered by setting the O_NONBLOCK flag on the file descriptor. In this case, if the connection can’t be made immediately, connect will fail with errno
set to EINPROGRESS and the connection will be made asynchronously.
While asynchronous connections can be tricky to handle, you can use a call to select on the socket file descriptor to indicate that the socket is ready for writing.

Closing a Socket

You can terminate a socket connection at the server and client by calling close, just as you would for low-level file descriptors. You should always close the socket at both ends. For the server, you should do this when read returns zero, but the close call could block if the socket has untransmitted data, is of a connection-oriented type, and has the SOCK_LINGER option set.

Basics Of SOCKET

What Is a Socket?

A socket is a communication mechanism that allows client/server systems to be developed either locally, on a single machine, or across networks. Linux functions such as printing and network utilities such as rlogin and ftp usually use sockets to communicate. Sockets are created and used differently from pipes because they make a clear distinction between client
and server. The socket mechanism can implement multiple clients attached to a single server.

Socket Connections


You can think of socket connections as telephone calls into a busy building. A call comes into an organi- zation and is answered by a receptionist who puts the call through to the correct department (the server process) and from there to the right person (the server socket). Each incoming call (client) is routed to an appropriate end point and the intervening operators are free to deal with further calls. Before you look at the way socket connections are established in Linux systems, you need to understand how they operate for socket applications that maintain a connection.
First of all, a server application creates a socket, which like a file descriptor is a resource assigned to the server process and that process alone. The server creates it using the system call socket, and it can’t be shared with other processes.
Next, the server process gives the socket a name. Local sockets are given a filename in the Linux file sys- tem, often to be found in /tmp or /usr/tmp. For network sockets, the filename will be a service identifier (port number/access point) relevant to the particular network to which the clients can connect. This identifier allows Linux to route incoming connections specifying a particular port number to the correct server process. A socket is named using the system call bind. The server process then waits for a client to connect to the named socket. The system call, listen, creates a queue for incoming connections. The server can accept them using the system call accept.
When the server calls accept, a new socket is created that is distinct from the named socket. This new socket is used solely for communication with this particular client. The named socket remains for further connections from other clients. If the server is written appropriately, it can take advantage of multiple connections. For a simple server, further clients wait on the listen queue until the server is ready again. The client side of a socket-based system is more straightforward. The client creates an unnamed socket by calling socket. It then calls connect to establish a connection with the server by using the server’s named socket as an address.
Once established, sockets can be used like low-level file descriptors, providing two-way data communications.







Socket Attributes

 
To fully understand the system calls used in this example, you need to learn a little about UNIX networking.
Sockets are characterized by three attributes: domain, type, and protocol. They also have an address used as their name. The formats of the addresses vary depending on the domain, also known as the protocol family. Each protocol family can use one or more address families to define the address format.

Socket Domains

 
Domains specify the network medium that the socket communication will use. The most common socket domain is AF_INET, which refers to Internet networking that’s used on many Linux local area networks and, of course, the Internet itself. The underlying protocol, Internet Protocol (IP), which only has one address family, imposes a particular way of specifying computers on a network. This is called the IP address.
Although names almost always refer to networked machines on the Internet, these are translated into lower-level IP addresses. An example IP address is 192.168.1.99. All IP addresses are represented by four numbers, each less than 256, a so-called dotted quad. When a client connects across a network via sockets, it needs the IP address of the server computer.
There may be several services available at the server computer. A client can address a particular service on a networked machine by using an IP port. A port is identified within the system by assigning a unique 16-bit integer and externally by the combination of IP address and port number. The sockets are communication end points that must be bound to ports before communication is possible.
Servers wait for connections on particular ports. Well-known services have allocated port numbers that are used by all Linux and UNIX machines. These are usually, but not always, numbers less than 1024. Examples are the printer spooler (515), rlogin (513), ftp (21), and httpd (80). The last of these is the server for the World Wide Web (WWW). Usually, port numbers less than 1024 are reserved for system services and may only be served by processes with superuser privileges. X/Open defines a constant in netdb.h, IPPORT_RESERVED, to stand for the highest reserved port number. Because there is a standard set of port numbers for standard services, computers can easily connect to each other without having to establish the correct port. Local services may use nonstandard port addresses. The domain in our first example is the UNIX file system domain, AF_UNIX, which can be used by sockets based on a single computer that perhaps isn’t networked. When this is so, the underlying protocol is file input/output and the addresses are absolute filenames. The address that you used for the server socket was server_socket, which you saw appear in the current directory when you ran the server application.
Other domains that might be used include AF_ISO for networks based on ISO standard protocols and AF_XNS for the Xerox Network System. We won’t cover these here.

Socket Types


A socket domain may have a number of different ways of communicating, each of which might have different characteristics. This isn’t an issue with AF_UNIX domain sockets, which provide a reliable two- way communication path. In networked domains, however, you need to be aware of the characteristics of the underlying network. Internet protocols provide two distinct levels of service: streams and datagrams.

Stream Sockets


Stream sockets (in some ways similar to standard input/output streams)  provide a connection that is a sequenced and reliable two-way byte stream. Thus, data sent is guaranteed not to be lost, duplicated, or reordered without an indication that an error has occurred. Large messages are fragmented, transmitted, and reassembled. This is like a file stream, as it accepts large amounts of data and writes it to the low- level disk in smaller blocks. Stream sockets have predictable behavior.
Stream sockets, specified by the type SOCK_STREAM, are implemented in the AF_INET domain by TCP/IP connections. They are also the usual type in the AF_UNIX domain. We’ll concentrate on SOCK_STREAM sockets in this chapter because they are more commonly used in programming network
applications.
TCP/IP stands for Transmission Control Protocol/Internet Protocol. IP is the low-level protocol for packets that provides routing through the network from one computer to another. TCP provides sequencing, flow control, and retransmission to ensure that large data transfers arrive with all of the data present and correct or with an appropriate error condition reported.
 

Datagram Sockets

In contrast, a datagram socket, specified by the type SOCK_DGRAM, doesn’t establish and maintain a connection. There is also a limit on the size of a datagram that can be sent. It’s transmitted as a single network message that may get lost, duplicated, or arrive out of sequence—ahead of datagrams sent after it.
Datagram sockets are implemented in the AF_INET domain by UDP/IP connections and provide anunsequenced, unreliable service. However, they are relatively inexpensive in terms of resources, since network connections need not be maintained. They’re fast because there is no associated connection setup time. UDP stands for User Datagram Protocol. Datagrams are useful for “single-shot” inquiries to information services, for providing regular status information, or for performing low-priority logging. They have the advantage that the death of a server doesn’t necessarily require a client restart. Because datagram-based servers usually retain no connection
information, they can be stopped and restarted without unduly disturbing their clients. For now, we leave the topic of datagrams; see the “Datagrams” section near the end of this chapter for more information on datagrams.

Shared Memory



Shared memory allows two unrelated processes to access the same logical memory. Shared memory is a very efficient way of transferring data between two running processes. Although the X/Open standard doesn’t require it, it’s probable that most implementations of shared memory arrange for the memory being shared between different processes to be the same physical memory.
Shared memory provides an efficient way of sharing and passing data between multiple processes. Since it provides no synchronization facilities, we usually need to use some other mechanism to synchronize access to the shared memory. Typically, we might use shared memory to provide efficient access to large areas of memory and pass small messages to synchronize access to that memory.
Shared memory is a special range of addresses that is created by IPC for one process and appears in the address space of that process. Other processes can then “attach” the same shared memory segment into their own address space. All processes can access the memory locations just as if the memory had been allocated by malloc. If one process writes to the shared memory, the changes immediately become visible to any other process that has access to the same shared memory.

The functions for shared memory resemble those for semaphores:

#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);

As with semaphores, the include files sys/types.h and sys/ipc.h are normally also required before shm.h is included.

shmget
We create shared memory using the shmget function:
int shmget(key_t key, size_t size, int shmflg);
As with semaphores, the program provides key, which effectively names the shared memory segment, and the shmget function returns a shared memory identifier that is used in subsequent shared memory functions. There’s a special key value, IPC_PRIVATE, that creates shared memory private to the process. You wouldn’t normally use this value, and, as with semaphores, you may find the private shared memory is not actually private on many Linux systems. The second parameter, size, specifies the amount of memory required in bytes. The third parameter, shmflg, consists of nine permission flags that are used in the same way as the mode flags for creating files. Aspecial bit defined by IPC_CREAT must be bitwise ORed with the permissions to create a new shared memory segment. It’s not an error to have the IPC_CREAT flag set and pass the key of an existing shared memory segment. The IPC_CREAT flag is silently ignored if it is not required. The permission flags are very useful with shared memory because they allow a process to create shared memory that can be written by processes owned by the creator of the shared memory but only read by processes that other users have created. We can use this to provide efficient read-only access to data by placing it in shared memory without the risk of its being changed by other users. If the shared memory is successfully created, shmget returns a nonnegative integer, the shared memory identifier. On failure, it returns –1.

shmat
When we first create a shared memory segment, it’s not accessible by any process. To enable access to the shared memory, we must attach it to the address space of a process. We do this with the shmat function:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
The first parameter, shm_id, is the shared memory identifier returned from shmget. The second parameter, shm_addr, is the address at which the shared memory is to be attached to the current process. This should almost always be a null pointer, which allows the system to choose the address at which the memory appears. The third parameter, shmflg, is a set of bitwise flags. The two possible values are SHM_RND, which, in conjunction with shm_addr, controls the address at which the shared memory is attached, and SHM_RDONLY, which makes the attached memory read-only. It’s very rare to need to control the address at which shared memory is attached; you should normally allow the system to choose an address for you, as doing otherwise will make the application highly hardware-dependent. If the shmat call is successful, it returns a pointer to the first byte of shared memory. On failure –1 is returned.
The shared memory will have read or write access depending on the owner (the creator of the shared memory), the permissions, and the owner of the current process. Permissions on shared memory are similar to the permissions on files. An exception to this rule arises if shmflg & SHM_RDONLY is true. Then the shared memory won’t be writable, even if permissions would have allowed write access.

shmdt
The shmdt function detaches the shared memory from the current process. It takes a pointer to the address returned by shmat. On success, it returns 0, on error –1. Note that detaching the shared memory doesn’t delete it; it just makes that memory unavailable to the current process.
shmctl
The control functions for shared memory are (thankfully) somewhat simpler than the more complex ones for semaphores:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
The shmid_ds structure has at least the following members:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
The first parameter, shm_id, is the identifier returned from shmget.

The first parameter, shm_id, is the identifier returned from shmget. The second parameter, command, is the action to take. It can take three values:

Command Description
IPC_STAT Sets the data in the shmid_ds structure to reflect the values associated with the shared memory.
IPC_SET Sets the values associated with the shared memory to those provided in the shmid_ds data structure, if the process has permission to do so.
IPC_RMID Deletes the shared memory segment.

The third parameter, buf, is a pointer to structure containing the modes and permissions for the shared memory.

On success, it returns 0, on failure, –1. X/Open doesn’t specify what happens if you attempt to delete a shared memory segment while it’s attached. Generally, a shared memory segment that is attached but deleted continues to function until it has been detached from the last process. However, because this behavior isn’t specified, it’s best not to rely on it.

message queue


We’ll now take a look at the third and final System V IPC facility: message queues. In many ways, message queues are like named pipes, but without the complexity associated with opening and closing the pipe. However, using messages doesn’t get us away from the problems that we have with named pipes, such as blocking on full pipes. Message queues provide a reasonably easy and efficient way of passing data between two unrelated processes. They have the advantage over named pipes that the message queue exists independently of both the sending and receiving processes, which removes some of the difficulties that occur in synchronizing the opening and closing of named pipes.

Message queues provide a way of sending a block of data from one process to another. Additionally, each block of data is considered to have a type, and a receiving process may receive blocks of data having different type values independently. The good news is that we can almost totally avoid the synchronization and blocking problems of named pipes by sending messages. Even better, we can “look ahead” for messages that are urgent in some way. The bad news is that, just like pipes, there’s a maximum size limit imposed on each block of data and also a limit on the maximum total size of all blocks on all queues throughout the system.
While stating that these limits are imposed, the X/Open specification offers no way of discovering what the limits are, except that exceeding them is a valid reason for some message queue functions to fail. Linux does have two defines, MSGMAX and MSGMNB, which define the maximum size in bytes of an individual message and the maximum size of a queue, respectively. These macros may be different or, for that matter, not even present on other systems.
The message queue function definitions are

 #include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

msgget
We create and access a message queue using the msgget function:
int msgget(key_t key, int msgflg);
The program must provide a key value that, as with other IPC facilities, names a particular message queue. The special value IPC_PRIVATE creates a private queue, which in theory is accessible only by the current process. As with semaphores and messages, on some Linux systems the message queue may not actually be private. Since a private queue has very little purpose, that’s not a significant problem. As before, the second parameter, msgflg, consists of nine permission flags. Aspecial bit defined by IPC_CREAT must be bitwise ORed with the permissions to create a new message queue. It’s not an error to set the IPC_CREAT flag and give the key of an existing message queue. The IPC_CREAT flag is silently ignored if the message queue already exists. The msgget function returns a positive number, the queue identifier, on success or –1 on failure.

msgsnd
The msgsnd function allows us to add a message to a message queue:
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
The structure of the message is constrained in two ways. First, it must be smaller than the system limit, and second, it must start with a long int, which will be used as a message type in the receive function. When you’re using messages, it’s best to define your message structure something like this:
struct my_message {
long int message_type;
/* The data you wish to transfer */
}
Since the message_type is used in message reception, you can’t simply ignore it. You must declare your data structure to include it, and it’s also wise to initialize it, so that it contains a known value. The first parameter, msqid, is the message queue identifier returned from a msgget function. The second parameter, msg_ptr, is a pointer to the message to be sent, which must start with a long int type as described previously.
The third parameter, msg_sz, is the size of the message pointed to by msg_ptr. This size must not
include the long int message type. The fourth parameter, msgflg, controls what happens if either the current message queue is full or the systemwide limit on queued messages has been reached. If msgflg has the IPC_NOWAIT flag set, the function will return immediately without sending the message and the return value will be –1. If the msgflg has the IPC_NOWAIT flag clear, the sending process will be suspended, waiting for space to become available in the queue. On success, the function returns 0, on failure –1. If the call is successful, a copy of the message data has been taken and placed on the message queue.

msgrcv
The msgrcv function retrieves messages from a message queue:
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
The first parameter, msqid, is the message queue identifier returned from a msgget function.
The second parameter, msg_ptr, is a pointer to the message to be received, which must start with a long int type as described above in the msgsnd function.
The third parameter, msg_sz, is the size of the message pointed to by msg_ptr, not including the long int message type.
The fourth parameter, msgtype, is a long int, which allows a simple form of reception priority to be implemented. If msgtype has the value 0, the first available message in the queue is retrieved. If it’s greater than zero, the first message with the same message type is retrieved. If it’s less than zero, the first message that has a type the same as or less than the absolute value of msgtype is retrieved. This sounds more complicated than it actually is in practice. If you simply want to retrieve messages in the order in which they were sent, set msgtype to 0. If you want to retrieve only messages with a specific message type, set msgtype equal to that value. If you want to receive messages with a type of n or smaller, set msgtype to -n. The fifth parameter, msgflg, controls what happens when no message of the appropriate type is waiting to be received. If the IPC_NOWAIT flag in msgflg is set, the call will return immediately with a return value of –1. If the IPC_NOWAIT flag of msgflg is clear, the process will be suspended, waiting for an appropriate type of message to arrive.
On success, msgrcv returns the number of bytes placed in the receive buffer, the message is copied into the user-allocated buffer pointed to by msg_ptr, and the data is deleted from the message queue. It returns –1 on error.

msgctl
The final message queue function is msgctl, which is very similar to that of the control function for
shared memory.
int msgctl(int msqid, int command, struct msqid_ds *buf);
The msqid_ds structure has at least the following members:
struct msqid_ds {
uid_t msg_perm.uid;
uid_t msg_perm.gid
mode_t msg_perm.mode;
}
The first parameter, msqid, is the identifier returned from msgget.
The second parameter, command, is the action to take. It can take three values:

Command Description
IPC_STAT Sets the data in the msqid_ds structure to reflect the values associated with the message queue.
IPC_SET If the process has permission to do so, this sets the values associated with the message queue to those provided in the msqid_ds data structure.
IPC_RMID Deletes the message queue.
0 is returned on success, –1 on failure. If a message queue is deleted while a process is waiting in a msgsnd or msgrcv function, the send or receive function will fail.

Signal Handling in Linux

Signals have a very precise lifecycle. First, a signal is raised (we sometimes also say it is sent or generated). The kernel then stores the signal until it is able to deliver it.
Finally, once it is free to do so, the kernel handles the signal as appropriate. The kernel can perform one of three actions, depending on what the process asked it to do:

Ignore the signal
No action is taken. There are two signals that cannot be ignored: SIGKILL and SIGSTOP. The reason for this is that the system administrator needs to be able to kill or stop processes, and it would be a circumvention of that right if a process could elect to ignore a SIGKILL (making it unkillable), or a SIGSTOP (making it unstoppable).

Catch and handle the signal
The kernel will suspend execution of the process’ current code path, and jump to a previously registered function. The process will then execute this function. Once the process returns from this function, it will jump back to wherever it was when it caught the signal. SIGINT and SIGTERM are two commonly caught signals. Processes catch SIGINT to handle the user generating the interrupt character—for example, a terminal might catch this signal and return to the main prompt. Processes catch SIGTERM to perform necessarily cleanup, such as disconnecting from the network, or removing temporary files, before terminating. SIGKILL and SIGSTOP cannot be caught.

Perform the default action

This action depends on the signal being sent. The default action is often to terminate the process. This is the case with SIGKILL, for instance. However, many signals are provided for specific purposes that concern programmers in particular situations, and these signals are ignored by default because many programs are not interested in them. We will look at the various signals and their default actions shortly.

Traditionally, when a signal was delivered, the function that handled the signal had no information about what had happened except for the fact that a particular signal had occurred. Nowadays, the kernel can provide a lot of context to programmers who want to receive it, and signals can even pass user-defined data, like later and more advanced IPC mechanisms.

Signals Used in Linux :


1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12   
47) SIGRTMIN+13 48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14   
51) SIGRTMAX-13    52) SIGRTMAX-12 53) SIGRTMAX-11    54) SIGRTMAX-10   
55) SIGRTMAX-9         56) SIGRTMAX-8    57) SIGRTMAX-7                  58) SIGRTMAX-6      
59) SIGRTMAX-5         60) SIGRTMAX-4         61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1       64) SIGRTMAX


The numbers in this shows the Signals number. Which we used during the programming.

Signal Handling
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signal_handler(int sig)
{
printf(“ I got signal %d\n”, sig);
(void) signal(SIGINT, SIG_DFL);
}
int main()
{
(void) signal(SIGINT, signal_handler);
while(1)
{
printf(“Hello World!\n”);
sleep(1);
}
return 0;
}
$ ./ctrlc1
Hello World!
Hello World!
Hello World!
Hello World!
^C
-I got signal 2
Hello World!
Hello World!
Hello World!
Hello World!
^C
$
As you can see from this example, the signal handling function takes a single integer parameter, the signal number that caused the function to be called. This can be useful if the same function is used to handle more than one signal. Here we print out the value of SIGINT, which on this system happens to have the value 2. You shouldn’t rely on traditional numeric values for signals; always use signal names in new programs.

It is not safe to call all functions, such as printf, from within a signal handler. A useful technique is to use a signal handler to set a flag and then check that flag from the main program and print a message if required. Toward the end of the chapter, you will find a list of calls that can safely be made inside signal handlers.

Sending Signals

A process may send a signal to another process, including itself, by calling kill. The call will fail if the program doesn’t have permission to send the signal, often because the target process is owned by another user. This is the program equivalent of the shell command of the same name.

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

The kill function sends the specified signal, sig, to the process whose identifier is given by pid. It returns 0 on success. To send a signal, the sending process must have permission to do so. Normally, this means that both processes must have the same user ID (that is, you can send a signal only to one of your own processes, although the superuser may send signals to any process). kill will fail, return -1, and set errno if the signal given is not a valid one (errno set to EINVAL), if it doesn’t have permission (EPERM), or if the specified process doesn’t exist (ESRCH).

Semaphores: A synchronization mechnism



When we write programs that use threads operating in multiuser systems, multiprocessing systems, or a combination of the two, we often discover that we have critical sections of code, where we need to ensure that a single process (or a single thread of execution) has exclusive access to a resource. Semaphores have a complex programming interface. Fortunately, we can easily provide ourselves with a much-simplified interface that is sufficient for most semaphore-programming problems. a semaphore is a special variable that takes only whole positive numbers and upon which only two operations are allowed: wait and signal. Since “wait” and “signal” already have special meanings in Linux and UNIX programming, we’ll use the original notation:

❑ P(semaphore variable) for wait
❑ V(semaphore variable) for signal

These letters come from the Dutch words for wait (passeren: to pass, as in a checkpoint before the critical section) and signal (vrijgeven: to give or release, as in giving up control of the critical section). You may also come across the terms “up” and “down” used in relation to semaphores, taken from the use of signaling flags.

Semaphore Definition

The simplest semaphore is a variable that can take only the values 0 and 1, a binary semaphore. This is the most common form. Semaphores that can take many positive values are called general semaphores. For the remainder of this chapter, we’ll concentrate on binary semaphores.
The definitions of P and V are surprisingly simple. Suppose we have a semaphore variable sv. The two operations are then defined as follows:
P(sv) If sv is greater than zero, decrement sv. If sv is zero, suspend execution of this process.
V(sv) If some other process has been suspended waiting for sv, make it resume execution. If no process is suspended waiting for sv, increment sv.
Another way of thinking about semaphores is that the semaphore variable, sv, is true when the critical section is available, is decremented by P(sv) so it’s false when the critical section is busy, and is incremented by V(sv) when the critical section is again available. Be aware that simply having a normal variable that you decrement and increment is not good enough, because you can’t express in C, C++, or almost any conventional programming language the need to make a single, atomic operation of the test to see whether the variable is true, and if so change the variable to make it false. This is what makes the semaphore operations special.

Linux Semaphore Facilities

Now that we’ve seen what semaphores are and how they work in theory, we can look at how the features are implemented in Linux. The interface is rather elaborate and offers far more facilities than are generally required. All the Linux semaphore functions operate on arrays of general semaphores rather than a single binary semaphore. At first sight, this just seems to make things more complicated, but in complex cases where a process needs to lock multiple resources, the ability to operate on an array of semaphores is a big advantage. In this chapter, we will concentrate on using single semaphores, since in most cases that’s all you will need to use.
The semaphore function definitions are

#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

As we work through each function in turn, remember that these functions were designed to work for arrays of semaphore values, which makes their operation significantly more complex than would have been required for a single semaphore.
Notice that key acts very much like a filename in that it represents a resource that programs may use and cooperate in using if they agree on a common name. Similarly, the identifier returned by semget and used by the other shared memory functions is very much like the FILE * file stream returned by fopen in that it’s a value used by the process to access the shared file. And, just as with files, different processes will have different semaphore identifiers, though they refer to the same semaphore. This use of a key and identifiers is common to all of the IPC facilities discussed here, although each facility uses independent keys and identifiers.

Semget

The semget function creates a new semaphore or obtains the semaphore key of an existing semaphore.

int semget(key_t key, int num_sems, int sem_flags);

The first parameter, key, is an integral value used to allow unrelated processes to access the same semaphore. All semaphores are accessed indirectly by the program supplying a key, for which the system then generates a semaphore identifier. The semaphore key is used only with semget. All other semaphore functions use the semaphore identifier returned from semget.
There is a special semaphore key value, IPC_PRIVATE (usually 0), that is intended to create a semaphore that only the creating process could access. This rarely has any useful purpose, which is fortunate, because on some Linux systems the manual pages list as a bug the fact that IPC_PRIVATE may not prevent other processes from accessing the semaphore. The num_sems parameter is the number of semaphores required. This is almost always 1. The sem_flags parameter is a set of flags, very much like the flags to the open function. The lower nine bits are the permissions for the semaphore, which behave like file permissions. In addition, these can be bitwise ORed with the value IPC_CREAT to create a new semaphore. It’s not an error to have the IPC_CREAT flag set and give the key of an existing semaphore. The IPC_CREAT flag is silently ignored if it is not required. We can use IPC_CREAT and IPC_EXCL together to ensure that we obtain a new, unique semaphore. It will return an error if the semaphore already exists.
The semget function returns a positive (nonzero) value on success; this is the semaphore identifier used in the other semaphore functions. On error, it returns –1.

semop

The function semop is used for changing the value of the semaphore:

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

The first parameter, sem_id, is the semaphore identifier, as returned from semget. The second parameter,
sem_ops, is a pointer to an array of structures, each of which will have at least the following members:

struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
}

The first member, sem_num, is the semaphore number, usually 0 unless you’re working with an array of semaphores. The sem_op member is the value by which the semaphore should be changed. (You can change a semaphore by amounts other than 1.) In general, only two values are used, –1, which is our P operation to wait for a semaphore to become available, and +1, which is our V operation to signal that a semaphore is now available. The final member, sem_flg, is usually set to SEM_UNDO. This causes the operating system to track the changes made to the semaphore by the current process and, if the process terminates without releasing the semaphore, allows the operating system to automatically release the semaphore if it was held by this process. It’s good practice to set sem_flg to SEM_UNDO, unless you specifically require different behavior. If you do decide you need a value other than SEM_UNDO, it’s important to be consistent, or you can get very confused as to whether the kernel is attempting to “tidy up” your semaphores when your process exits. All actions called for by semop are taken together to avoid a race condition implied by the use of multiple semaphores. You can find full details of the processing of semop in the manual pages.

Semctl

The semctl function allows direct control of semaphore information:
int semctl(int sem_id, int sem_num, int command, ...);
The first parameter, sem_id, is a semaphore identifier, obtained from semget. The sem_num parameter is the semaphore number. You use this when you’re working with arrays of semaphores. Usually, this is 0, the first and only semaphore. The command parameter is the action to take, and a fourth parameter, if present, is a union semun, which according to the X/OPEN specification, must have at least the following members:

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}
Many versions of Linux have a definition of the semun union in a header file (usually sem.h), though X/Open does say that you have to declare your own. If you do find that you need to declare your own, check the manual pages for semctl to see if there is a definition given. If there is, we suggest you use exactly the definition given in your manual, even if it differs from that above. There are many different possible values of command allowed for semctl. Only the two that we describe here are commonly used. For full details of the semctl function, you should consult the manual page. The two common values of command are
❑ SETVAL: Used for initializing a semaphore to a known value. The value required is passed as
the val member of the union semun. This is required to set the semaphore up before it’s used for the first time.
❑ IPC_RMID: Used for deleting a semaphore identifier when it’s no longer required. The semctl function returns different values depending on the command parameter. For SETVAL and IPC_RMID it returns 0 for success and –1 on error.

File I/O : system calls

File I/O

Opening Files

The most basic method of accessing a file is via the read( ) and write( ) system calls. Before a file can be accessed, however, it must be opened via an open( ) or creat( ) system call. Once done using the file, it should be closed using the system call close( ).

The open( ) System Call

A file is opened, and a file descriptor is obtained with the open( ) system call:

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

int   open (const char *name, int flags);
int   open (const char *name, int flags, mode_t mode);


The open( ) system call maps the file given by the pathname name to a file descriptor, which it returns on success. The file position is set to zero, and the file is opened for access according to the flags given by flags.

Flags for open( )

The flags argument must be one of O_RDONLY, O_WRONLY, or O_RDWR. Respectively, these arguments request that the file be opened only for reading, only for writing, or for both reading and writing.

For example, the following code opens /home/vineet/test  for reading:

Int  fd;
fd = open ("/home/vineet/test", O_RDONLY);
if (fd == -1)
/* error */
 A file opened only for writing cannot  also be read, and vice versa. The process issuing the open( )  system call must have sufficient permissions to obtain the access requested.

The flags  argument can be bitwise-ORed with one or more of the following values, modifying the behavior of the open request:

O_APPEND
 The file will be opened in append mode. That is, before each write, the file position will be updated to point to the end of the file. This occurs even if another process has written to the file after the issuing process’ last write, thereby changing the file position.

O_ASYNC
A signal (SIGIO by default) will be generated when the specified file becomes readable or writable. This flag is available only for terminals and sockets, not for regular files.

O_CREAT
If the file denoted by name does not exist, the kernel will create it. If the file already exists, this flag has no effect unless O_EXCL is also given.

O_DIRECT
The file will be opened for direct I/O .

O_DIRECTORY
If name is not a directory, the call to open( ) will fail. This flag is used internally by the opendir( ) library call.

O_EXCL
When given with O_CREAT, this flag will cause the call to open( ) to fail if the file given by name already exists. This is used to prevent race conditions on file creation.

O_LARGEFILE
The given file will be opened using 64-bit offsets, allowing files larger than two gigabytes to be opened. This is implied on 64-bit architectures.

O_NOCTTY
If the given name refers to a terminal device (say, /dev/tty), it will not become the process controlling terminal, even if the process does not currently have a controlling terminal. This flag is not frequently used.

O_NOFOLLOW
If name is a symbolic link, the call to open( ) will fail. Normally, the link is resolved, and the target file is opened. If other components in the given path are links, the call will still succeed. For example, if name is /etc/ship/plank.txt, the call will fail if plank.txt is a symbolic link. It will succeed, however, if etc or ship is a symbolic link, so long as plank.txt is not.

O_NONBLOCK
If possible, the file will be opened in nonblocking mode. Neither the open( ) call, nor any other operation will cause the process to block (sleep) on the I/O. This behavior may be defined only for FIFOs.

O_SYNC
The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk; normal read operations are already synchronous, so this flag has no effect on reads. POSIX additionally defines O_DSYNC and O_RSYNC; on Linux, these flags are synonymous with O_SYNC.

O_TRUNC
If the file exists, it is a regular file, and the given flags allow for writing, the file will be truncated to zero length. Use of O_TRUNC on a FIFO or terminal device is ignored. Use on other file types is undefined. Specifying O_TRUNC with O_RDONLY is also undefined, as you need write access to the file in order to truncate it.

For example, the following code opens for writing the file /home/teach/pearl. If the file already exists, it will be truncated to a length of zero. Because the O_CREAT flag is not specified, if the file does not exist, the call will fail:

int   fd;
fd = open ("/home/vineet/test1", O_WRONLY | O_TRUNC);
if (fd == -1)
/* error */



Owners of New Files

S_IRWXU
Owner has read, write, and execute permission.
S_IRUSR
Owner has read permission.
S_IWUSR
Owner has write permission.
S_IXUSR
Owner has execute permission.
S_IRWXG
Group has read, write, and execute permission.
S_IRGRP
Group has read permission.
S_IWGRP
Group has write permission.
S_IXGRP
Group has execute permission.
S_IRWXO
Everyone else has read, write, and execute permission.
S_IROTH
Everyone else has read permission.

S_IWOTH
Everyone else has write permission.
S_IXOTH
Everyone else has execute permission.



The creat( ) Function

The combination of O_WRONLY | O_CREAT | O_TRUNC is so common that a system call exists to provide just that behavior:

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

int  creat (const char *name, mode_t mode);


Return Values and Error Codes
Both open( ) and creat( ) return a file descriptor on success. On error, both return -1, and set errno to an appropriate error value (Chapter 1 discussed errno and listed the potential error values). Handling an error on file open is not complicated, as generally there will have been few or no steps performed prior to the open that need to be undone. A typical response would be prompting the user for a different filename or
simply terminating the program.


Reading via read( )

The most basic—and common—mechanism used for reading is the read( ) system call.

#include <unistd.h>
ssize_t read (int  fd, void *buf, size_t len);

Each call reads up to len bytes into buf from the current file offset of the file referenced by fd. On success, the number of bytes written into buf is returned. On error, the call returns -1, and errno is set. The file position is advanced by the number of bytes read from fd. If the object represented by fd is not capable of seeking (for example, a character device file), the read always occurs from the “current” position.
Basic usage is simple. This example reads from the file descriptor fd  into word . The number of bytes read is equal to the size of the unsigned long  type, which is four bytes on 32-bit Linux systems, and eight bytes on 64-bit systems. On return, nr  contains the number of bytes read, or -1  on error:


unsigned long word;
ssize_t  nr;

/* read a couple bytes into 'word' from 'fd' */
nr = read (fd, &word, sizeof (unsigned long));
if (nr == -1)
/* error */

 There are two problems with this -ve implementation: the call might return without reading all len  bytes, and it could produce certain errors that this code does not check for and handle.

Writing with write( )

The most basic and common system call used for writing is write( ). write( ) is the counterpart of read( ).

#include <unistd.h>
ssize_t  write (int fd, const void *buf, size_t count);

A call to write( ) writes up to count bytes starting at buf to the current file position of the file referenced by the file descriptor fd. Files backed by objects that do not support seeking (for example, character devices) always write starting at the “head.”

On success, the number of bytes written is returned, and the file position is updated in kind. On error, -1 is returned, and errno is set appropriately. A call to write( ) can return 0, but this return value does not have any special meaning; it simply implies that zero bytes were written.
As with read( ), the most basic usage is simple:

const char *buf = "My ship is solid!";
ssize_t  nr;
/* write the string in 'buf' to 'fd' */
nr = write (fd, buf, strlen (buf));
if (nr == -1)
/* error */

But again, as with read( ) , this usage is not quite right. Callers also need to check for the possible occurrence of a partial write:

unsigned long word = 1720;
size_t  count;
ssize_t  nr;
count = sizeof (word);
nr = write (fd, &word, count);
if (nr == -1)
/* error, check errno */
else if (nr != count)
/* possible error, but 'errno' not set */
   

Closing Files
After a program has finished working with a file descriptor, it can unmap the file descriptor from the associated file via the close( ) system call:

#include <unistd.h>
Int  close (int fd);
A call to close( ) unmaps the open file descriptor fd, and disassociates the process from the file. The given file descriptor is then no longer valid, and the kernel is free to reuse it as the return value to a subsequent open( ) or creat( ) call. A call to close( ) returns 0 on success. On error, it returns -1, and sets errno appropriately. Usage is simple:

if (close (fd) == -1)
perror ("close");

Note that closing a file has no bearing on when the file is flushed to disk. If an application wants to ensure that the file is committed to disk before closing it, it needs to make use of one of the synchronization options discussed earlier in “Synchronized I/O.”
Closing a file does have some side effects, though. When the last open file descriptor referring to a file is closed, the data structure representing the file inside the kernel is freed. When this data structure is freed, it unpins the in-memory copy of the inode associated with the file. If nothing else is pinning the inode, it too may be freed from memory (it may stick around because the kernel caches inodes for performance reasons, but it need not). If a file has been unlinked from the disk, but was kept open before it was unlinked, it is not physically removed until it is closed and its inode is removed from memory. Therefore, calling close( ) may also result in an unlinked file finally being physically removed from the disk.

Named Pipes: FIFOs

FIFO

A named pipe is a special type of file (remember that everything in Linux is a file!) that exists as a name in the file system but behaves like the unnamed
pipes that we’ve met already.

We can create named pipes from the command line and from within a program. Historically, the command line program for creating them was mknod:

$ mknod filename p

However, the mknod command is not in the X/Open command list, so it may not be available on all UNIX-like systems. The preferred command line method is to use

$ mkfifo filename

Some older versions of UNIX only had the mknod command. X/Open Issue 4 Version 2 has the mknod function call, but not the command line program. Linux, friendly as ever, supplies both mknod and mkfifo.

From inside a program, we can use two different calls:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0);

Like the mknod command, you can use the mknod function for making many special types of files. Using a dev_t value of 0 and ORing the file access mode with S_IFIFO is the only portable use of this function that creates a named pipe. We’ll use the simpler mkfifo function in our examples


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo(“/tmp/my_fifo”, 0777);
if (res == 0) printf(“FIFO created\n”);
exit(EXIT_SUCCESS);
}



We can look for the pipe with

$ ls -lF /tmp/my_fifo

prwxr-xr-x 1 vineet embedyourcareer  0 march 18 14:55 /tmp/my_fifo|

Notice that the first character of output is a p, indicating a pipe. The | symbol at the end is added by the ls command’s -F option and also indicates a pipe.

How It Works

The program uses the mkfifo function to create a special file. Although we ask for a mode of 0777, this is altered by the user mask (umask) setting (in this case 022), just as in normal file creation, so the resulting file has mode 755. If your umask is set differently, for example to 0002, you will see different permissions on the created file.
We can remove the FIFO just like a conventional file by using the rm command, or from within a program by using the unlink system call.


Basics of Inter process communication- PIPE


Inter Process Communication


PIPE:

We use the term pipe when we connect a data flow from one process to another. Generally we attach, or pipe, the output of one process to the input of another.

Most Linux users will already be familiar with the idea of linking shell commands together, so that the output of one process is fed straight to the input of another. For shell commands, this is entered as
cmd1 | cmd2



The shell arranges the standard input and output of the two commands, so that
❑ The standard input to cmd1 comes from the terminal keyboard.
❑ The standard output from cmd1 is fed to cmd2 as its standard input.
❑ The standard output from cmd2 is connected to the terminal screen.



What the shell has done, in effect, is reconnect the standard input and output streams so that data flows from the keyboard input through the two commands and is then output to the screen.



The PIPE Call:

The pipe function has the following prototype:

#include <unistd.h>
int pipe(int file_descriptor[2]);

pipe is passed (a pointer to) an array of two integer file descriptors. It fills the array with two new file descriptors and returns a zero. On failure, it returns -1 and sets errno to indicate the reason for failure. Errors defined in the Linux manual page (section 2 of the manual) are

❑ EMFILE: Too many file descriptors are in use by the process.
❑ ENFILE: The system file table is full.
❑ EFAULT: The file descriptor is not valid.

The two file descriptors returned are connected in a special way. Any data written to file_descriptor[1] can be read back from file_descriptor[0]. The data is processed in a first in, first out basis, usually abbreviated to FIFO. This means that if you write the bytes 1, 2, 3 to file_descriptor[1], reading from file_descriptor[0] will produce 1, 2, 3. This is different from a stack, which operates on a last in, first out basis, usually abbreviated to LIFO.




Here’s a program, pipe1.c, that uses pipe to create a pipe.


Type the following code.

Note the file_pipes pointer, which is passed to the pipe function as a parameter.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = “123”;
char buffer[BUFSIZ + 1];

memset(buffer, ‘\0’, sizeof(buffer));

if (pipe(file_pipes) == 0)
{
data_processed = write(file_pipes[1], some_data, strlen(some_data));
printf(“Wrote %d bytes\n”, data_processed);
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf(“Read %d bytes: %s\n”, data_processed, buffer);
exit(EXIT_SUCCESS);
}

exit(EXIT_FAILURE);
}


When we run this program, the output is
$ ./pipe1
Wrote 3 bytes
Read 3 bytes: 123



How It Works

The program creates a pipe using the two file descriptors file_pipes. It then writes data into the pipe using the file descriptor file_pipes and reads it back from file_pipes. Notice that the pipe has some internal buffering that stores the data in between the calls to write and read.

You should be aware that the effect of trying to write using file_descriptor, or read using file_descriptor, is undefined, so the behavior could be very strange and may change without warning. On the authors’ systems, such calls fail with a –1 return value, which at least ensures that it’s easy to catch this mistake.

At first glance, this example of a pipe doesn’t seem to offer us anything that we couldn’t have done with a simple file. The real advantage of pipes comes when you wish to pass data between two processes.