It is absolutely impossible to describe interprocess communication in detail here, and it is difficult for the author to have confidence in his understanding of this part, so at the beginning of this section, I first recommend Richard Stevens' famous work: Advanced Programming in UNIX Environment. Its Chinese translation "Advanced Programming in UNIX Environment" has been published by Machinery Industry Press. The original text is wonderful and the translation is equally authentic. If you are really interested in programming under Linux, please put this book on your desk or next to your computer. Having said that, it's really hard to restrain my admiration and get to the point. In this section, we will introduce some elementary and simple knowledge and concepts of interprocess communication.
First of all, the communication between processes can be realized at least by transmitting open files, and different processes transmit information through one or more files. In fact, this method is used in many application systems. But generally speaking, IPC (Inter-process Communication) does not include this seemingly bottom-level communication mode. There are many methods to realize inter-process communication in Unix system, but unfortunately there are few methods that can be transplanted in all Unix systems (the only one is half-duplex pipeline, which is also the most primitive communication method). As a new operating system, Linux supports almost all common interprocess communication methods under Unix: pipeline, message queue, * * * * shared memory, semaphore, windows sockets and so on. We will introduce them one by one.
2.3. 1 pipeline
Pipeline is the oldest interprocess communication method, including unknown pipeline and famous pipeline. The former is used for communication between the parent process and the child process, and the latter is used for communication between any two processes running on the same machine.
Nameless pipes are created by the pipe () function:
# include & ltunistd.h & gt
int pipe(int filedis[2]);
Parameter filedis returns two file descriptors: filedes[0] is open for reading and filedes[ 1] is open for writing. The output of filedes[ 1] is the input of filedes[0]. The following example demonstrates how to realize communication between parent process and child process.
# Definition Input 0
# Define output 1
void main() {
int file _ descriptors[2];
/* Define sub-process number */
pid _ t pid
char buf[256];
int returned _ count
/* Create an unnamed pipe */
Pipeline (file descriptor);
/* Create a child process */
if((pid = fork()) == - 1) {
Printf ("Error in fork/n");
Exit (1);
}
/* Execute subprocess */
if(pid == 0) {
Printf ("In the derivative (child) process .../n");
/* The child process writes data to the parent process and closes the reading end of the pipeline */
close(file _ descriptors[INPUT]);
Write(file_descriptors[OUTPUT], "test data", strlen);
Exit (0);
} Otherwise {
/* Execute parent process */
Printf ("In the process of spawning (father) .../n");
/* The parent process reads the data written by the child process from the pipeline and closes the writing end of the pipeline */
close(file _ descriptors[OUTPUT]);
returned _ count = read(file _ descriptor[INPUT],buf,sizeof(buf));
Printf("%d bytes of data received from the derivation process: %s/n ",
returned_count,buf);
}
}
Under the Linux system, the famous pipeline can be created in two ways: command line mknod system call and function mkfifo. The following two methods generate a famous pipeline named myfifo in the current directory:
Mode 1: mkfifo(“my FIFO ","rw ");
Method 2: mknod myfifo p
After the famous pipeline is generated, you can use general file I/O functions such as open, close, read and write to operate it. Here is a simple example. Suppose we create a famous pipeline named myfifo.
/* Process 1: Read famous pipeline */
# include & ltstdio.h & gt
# include & ltunistd.h & gt
void main() {
FILE * in _ file
int count = 1;
char buf[80];
in_file = fopen("mypipe "," r ");
if (in_file == NULL) {
Error in printf(" FD open. /n ");
Exit (1);
}
while ((count = fread(buf, 1,80,in _ file))& gt; 0)
Printf ("received from pipeline: %s/n", BUF);
fclose(in _ file);
}
/* Process 2: Write Named Pipes */
# include & ltstdio.h & gt
# include & ltunistd.h & gt
void main() {
FILE * out _ file
int count = 1;
char buf[80];
out_file = fopen("mypipe "," w ");
if (out_file == NULL) {
Printf ("Error opening pipeline." );
Exit (1);
}
Sprintf(buf, "This is the test data of the named pipeline example/n");
fwrite(buf, 1,80,out _ file);
fclose(out _ file);
}
2.3.2 Message Queuing
Message Queuing is used for inter-process communication running on the same machine. It is similar to a pipe. It is a queue for saving messages in the system kernel. It appears in the system kernel in the form of message linked list. The node structure in the message list is declared by msg.
In fact, it is a communication mode that is gradually being eliminated, and we can replace it with stream pipe or windows socket. Therefore, this mode is not explained, and readers are advised to ignore it.
2.3.3 *** Enjoy the memory.
* * * Shared memory is the fastest way to communicate between processes running on the same machine, because there is no need to copy data between different processes. Usually one process creates a * * * shared memory area, and other processes read and write this memory area. There are two ways to get * * * shared memory: mapping /dev/mem devices and memory image files. The former method will not bring extra overhead to the system, but it is not commonly used in reality because it will control the access to the actual physical memory. Under the Linux system, this can only be achieved by limiting the memory accessed by the Linux system, which is of course not practical. The common way is to use * * * shared memory for storage through shmXXX function family.
The first function to use is shmget, which gets a * * * shared storage identifier.
# include & ltsys/types . h & gt;
# include & ltsys/IPC . h & gt;
# include & ltsys/shm . h & gt;
int shmget(key_t key,int size,int flag);
This function is somewhat similar to the familiar malloc function. The system allocates the memory of size as * * * shared memory according to the request. Each IPC structure in the Linux kernel has a non-negative integer identifier, so when sending messages to the message queue, you only need to refer to the identifier. This identifier is obtained by the kernel from the keyword of IPC structure. This keyword is the key to the first function above. The data type key_t is defined in the header file sys/types.h and is a long integer data. In our later chapters, we will also encounter this keyword.
When creating memory, other processes can call shmat () to connect it to their own address space.
void *shmat(int shmid,void *addr,int flag);
Shmid is the * * * shared storage identifier returned by the shmget function. Addr and flag parameters determine how to determine the connection address. The return value of the function is the actual address where the data segment of the process is connected, and the process can read and write the process.
Synchronization of data access is the key to realize inter-process communication using * * * shared storage. It is necessary to ensure that when a process reads data, the data it wants has been written. Semaphores are usually needed to synchronize access to * * * shared storage data. In addition, some flag bits of * * shared storage memory can be set by using shmctl function, such as SHM_LOCK and SHM_UNLOCK.
semaphore
Semaphore, also known as semaphore, is used to coordinate data objects between different processes. The most important application is the inter-process communication with memory sharing mentioned in the previous chapter. A semaphore is essentially a counter, which is used to record the access to a resource (such as * * * shared memory). Generally speaking, in order to obtain * * * resources, a process needs to do the following:
(1) Test the signal that controls this resource.
(2) If the value of the semaphore is positive, the resource is allowed to be used. The process subtracts 1 from the semaphore.
(3) If the semaphore is 0, the resource is currently unavailable, and the process enters a sleep state until the semaphore is greater than 0, the process is awakened, and the process enters step (1).
(4) When the process no longer uses the resources controlled by the semaphore, the semaphore increases by 1. If a process is sleeping and waiting for this semaphore, wake the process.
It is the Linux kernel operating system that maintains the semaphore state, not the user process. We can see the definitions of various structures used by the kernel to maintain the semaphore state from the header file/usr/src/Linux/include/Linux/sem.h. A semaphore is a collection of data, and users can use each element of this collection independently. The first function to be called is semget to get the semaphore ID.
Structural sem {
Short sempid/* PID of last operation */
Usort semval/* Current value */
Usort semncnt/* Number of processes waiting to increase in semval */
Usport semzcnt/* Number of processes waiting for semval = 0 */
}
# include & ltsys/types . h & gt;
# include & ltsys/IPC . h & gt;
# include & ltsys/SEM . h & gt;
int semget(key_t key,int nsems,int flag);
Key is the keyword of IPC structure mentioned above. The flag will determine whether to create a new semaphore set or reference an existing semaphore set in the future. Nsems is the number of semaphores in the collection. If you are creating a new collection (usually in the server), you must specify nsems;; If it refers to an existing semaphore set (usually in the client), specify nsems as 0.
Semctl function is used to operate on semaphores.
int semctl(int semid,int semnum,int cmd,union semun arg);
Different operations are realized through cmd parameters. Seven different operations are defined in the header file sem.h, which can be used as a reference in actual programming.
The semop function automatically executes the array of operations on the semaphore collection.
int semop(int semid,struct sembuf semoparray[],size _ t nops);
Semoparray is a pointer to an array of semaphore operations. Nops specifies the operands in the array.
Next, let's look at a concrete example. It creates a keyword with a specific IPC structure and a semaphore, establishes the index of this semaphore, modifies the value of the semaphore pointed by the index, and finally clears the semaphore. In the following code, the function ftok generates the unique IPC keyword we mentioned above.
# include & ltstdio.h & gt
# include & ltsys/types . h & gt;
# include & ltsys/SEM . h & gt;
# include & ltsys/IPC . h & gt;
void main() {
Key_t unique key; /* define an IPC keyword */
int id
struct sembuf lock _ it
union semun options
int I;
unique_key = ftok(",",' a '); /* Generate keywords, and the character "a" is a random seed */
/* Create a new semaphore collection */
id = semget(unique_key, 1,IPC _ CREAT | IPC _ EXCL | 0666);
Printf ("semaphore id=%d/n", ID);
options . val = 1; /* Set variable value */
semctl(id,0,SETVAL,options); /* Set the semaphore of index 0 */
/* Print the value of the semaphore */
i = semctl(id,0,GETVAL,0);
Printf ("The value of the semaphore at index 0 is %d/n", i);
/* Reset the following traffic lights */
lock _ it . SEM _ num = 0; /* Set which semaphore */
lock _ it . SEM _ op =- 1; /* Define operation */
Lock _ it.sem _ flg = IPC _ NOWAIT/* operation mode */
if (semop(id,& amplock_it, 1) == - 1) {
Printf ("Unable to lock semaphore. /n ");
Exit (1);
}
i = semctl(id,0,GETVAL,0);
Printf ("The value of the semaphore at index 0 is %d/n", i);
/* Clear semaphore */
semctl(id,0,IPC_RMID,0);
}