4. Inter-process-communication
4.1 Signals
In the Linux system, signals are an asynchronous notification mechanism used for passing events and information between processes or between the operating system and processes. Processes can respond to external events, such as user input, hardware exceptions, or actions from other processes, through signals.
User processes have three ways to respond to signals:
- Ignore the signal: The process takes no action when the signal is received. However, there are two signals that cannot be ignored, namely SIGKILL and SIGSTOP. SIGKILL is used to immediately terminate the execution of a process, while SIGSTOP is used to pause a process's execution
- Catch the signal: Define a signal handler function to execute a custom action when the signal is received. Applications can use system calls like signal() or sigaction() to register signal handler functions and respond to specific signals
- Perform the default action: Linux defines default actions for each type of signal. For example, when a process receives the SIGTERM signal, Linux defaults to terminating the execution of that process. Applications can choose not to define a signal handler function and let the system perform the default action for specific signals
The process from signal generation to handling includes the following steps:
- Signal generation: Signals can be triggered by various events, such as hardware interrupts, software exceptions, or user key presses. When an event occurs, the Linux kernel automatically generates the corresponding signal and sends it to the target process
- Signal delivery: The process generating the signal sends it to a specific target process, usually using system calls like kill() or sigqueue(). The sending process needs to know the target process's Process ID (PID) and specify the signal type and other parameters
- Signal reception: When the target process receives the signal, the operating system checks how this process handles the signal. If the signal is blocked or ignored by the process, the operating system stores the signal in the process's signal queue, waiting for the target process to unblock or stop ignoring the signal before processing it
- Signal handling: If the target process has not handled the signal specifically or the signal is not blocked or ignored, the operating system calls the target process's signal handler function to process the signal. Each process can set its own signal handler function, and when the process receives a signal, the operating system automatically calls the corresponding signal handler function
- Signal handling options: Each process can set its own signal handling options, including the signal handler function, the way to block signals, and the way to ignore signals. Some signals cannot be blocked or ignored, such as SIGKILL and SIGSTOP
- Signal priorities: Signals in Linux have priorities, where signals with lower numbers have higher priorities. For example, SIGKILL has a priority of 9, while SIGINT has a priority of 2. When a process receives multiple signals simultaneously, the operating system prioritizes signals based on their priorities to decide which signal to handle first
- Default signal handling: For each signal type, the Linux kernel defines a default handling action. For example, for the SIGINT signal (usually generated by the user pressing Ctrl+C on the terminal), the default action is to terminate the target process. However, processes can set their own signal handling options, including defining signal handler functions and blocking or ignoring signals, by calling functions like sigaction
If you want to view the signals already defined in Linux, enter the following in the terminal:
linaro@linaro-alip:~$ kill -l
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) SIGRTMAXCommonly used signals in the system:
Signal Signal Value Default Action Description SIGHUP 1 Terminate Hangup signal, the controlling terminal is closed SIGINT 2 Terminate Interrupt signal (Ctrl-C) SIGQUIT 3 Terminate with core dump Quit signal (Ctrl-) SIGILL 4 Terminate with core dump Illegal instruction SIGTRAP 5 Terminate with core dump Trace trap SIGABRT 6 Terminate with core dump Abort signal from abort() function SIGBUS 7 Terminate with core dump Bus error SIGFPE 8 Terminate with core dump Floating-point arithmetic error SIGKILL 9 Terminate Kill signal, unblockable SIGUSR1 10 Terminate User-defined signal 1 SIGSEGV 11 Terminate with core dump Invalid memory reference SIGUSR2 12 Terminate User-defined signal 2 SIGPIPE 13 Terminate Write on a pipe with no reader SIGALRM 14 Terminate Alarm clock signal SIGTERM 15 Terminate Software termination signal, can be caught by the process SIGSTKFLT 16 Terminate (b) Stack fault SIGCHLD 17 Ignore Child process has terminated SIGCONT 18 Ignore Continue executing, if stopped SIGSTOP 19 Stop Stop the process SIGSTP 20 Stop Stop the process (Ctrl-Z) SIGTTIN 21 Stop Background process attempting read from terminal SIGTTOU 22 Stop Background process attempting write to terminal SIGURG 23 Ignore Urgent condition on socket SIGXCPU 24 Terminate with core dump CPU time limit exceeded SIGXFSZ 25 Terminate with core dump File size limit exceeded SIGVTALRM 26 Terminate Virtual timer expired SIGPROF 27 Terminate Profile timer signal SIGWINCH 28 Ignore Window size change SIGIO 29 Terminate (a) I/O is available for asynchronous system calls (Ctrl-C) SIGPWR 30 Terminate Power failure SIGSYS 31 Terminate with core dump Bad system call
4.1.1 Sending Signals
Functions for sending signals mainly include kill(), raise(), alarm(), and pause().
kill()Function Declaration:#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);- The
pidparameter specifies the process ID of the recipient of the signal - The
sigparameter specifies the type of signal to be sent
- Example of
kill()Function:
#include <signal.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t pid;
int sig;
if(argc < 3){
printf("Usage:%s <pid_t> <signal>\n",argv[0]);
return -1;
}
// Convert strings to integers
sig = atoi(argv[2]);
pid = atoi(argv[1]);
kill(pid,sig);
return 0;
}- Compilation and Execution:
gcc kill.c -o kill
./kill- The
The
raise()function allows a process to send a specified signal to itself. Function declaration:#include <signal.h>
int raise(int sig);- The
sigparameter specifies the type of signal to be sent.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(void)
{
printf("raise before\n");
raise(9);
printf("raise after\n");
return 0;
}- Execution Result:
pi@raspberrypi:-/Linux/signal $./raise
raise before
Killed- The program prints "raise before," uses the
raise()function to send a stop signal, and then the process terminates.
- The
The
alarm()function can send a SIGALRM signal to the current process after a specified time. Function declaration:#include <unistd.h>
unsigned int alarm(unsigned int seconds);- The
secondsparameter specifies the number of seconds for the timer. Thealarm()function will send a SIGALRM signal to the current process after the specified seconds.
Example of
alarm()Function:#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig) {
printf("Received signal %d\n", sig);
}
int main() {
signal(SIGALRM, handler); // Register the signal handler
alarm(5); // Set a timer, trigger SIGALRM signal after 5 seconds
printf("Waiting for alarm to go off...\n");
pause(); // Block the process and wait for the signal to be triggered
printf("Exiting...\n");
return 0;
}Compilation and Execution:
gcc alarm.c -o alarm
./alarmExecution Result:
pi@raspberrypi:~/Linux/signal s./alarm
waiting for alarm to go off...
Received signal 14
Exiting...
- In the above example program, we first registered the signal handling function
handler(), and then calledalarm(5)to set a 5-second timer. During the waiting period for the timer to trigger, we use thepause()function to block the process and wait for the signal to be triggered. When the timer is triggered, it automatically sends the SIGALRM signal to the process, triggering the signal handling function, and the program prints the received signal number before exiting.
- The
4.1.2 Receiving Signals
- To receive signals, the process that receives the signal must not stop. Typically, while loops, sleep, and pause are used.
In Linux, the system call
signal()is used to set a new signal handler for a signal, which can be set as a user-specified function. The function declaration is as follows:#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);signum: The signal we want to handle. You can check the system's signals by typingkill -lin the terminal.handler: The method of handling the signal (system default, ignore, or capture).
*signal(SIGINT, SIG_IGN); // SIG_IGN means ignore the SIGINT signal, which is usually generated by pressing Ctrl+C or Delete.
*signal(SIGINT, SIG_DFL); // SIG_DFL means perform the system default action, which is to terminate the process for most signals.
*signal(SIGINT, handler); // Capture the SIGINT signal and execute the code in the handler function, which we define ourselves.Customizing the handling of the SIGINT signal:
#include <stdio.h>
#include <signal.h>
void signal_handler_fun(int signum)
{
printf("catch signal %d\n", signum);
}
int main(int argc, char *argv[])
{
signal(SIGINT, signal_handler_fun);
while (1);
return 0;
}- When Ctrl+C is pressed, the
signal_handler_funfunction is executed instead of exiting the program.
- When Ctrl+C is pressed, the
4.2 Pipes
In the Linux system, pipes are used to establish communication between reading and writing processes by sharing a file, hence they are also known as pipe files.there are two types of pipes: anonymous pipes and named pipes.
Anonymous Pipes: An anonymous pipe is a simple half-duplex pipe that can only be used for communication between processes with a parent-child relationship. Once created, the anonymous pipe becomes shared file descriptors between the two processes, where one process writes data to the pipe and the other process reads data from it. The lifetime of an anonymous pipe is associated with the processes that created it, and when the creating process terminates, the anonymous pipe is destroyed.
Named Pipes: Named pipes, also known as FIFO (First In, First Out), are a special type of file that allows any process to access it using its filename at any time. It provides a communication mechanism between processes that are not necessarily related and can allow multiple processes to access it simultaneously, making it suitable for interprocess communication over networks. Named pipes persist in the file system until they are deleted or the system is shut down. Any process with permission to access the named pipe can write to and read from it.
In summary, anonymous pipes are suitable for communication between related processes, while named pipes are suitable for communication between unrelated processes and have persistence.
4.2.1 Anonymous Pipes
Characteristics of anonymous pipes:
- Pipes use half-duplex communication, allowing data transmission in only one direction at a time
- Pipes are actually fixed-size buffers, and if a process writes to a full pipe, the system will block that process until there is space in the pipe to receive the data
- The pipe communication mechanism must provide synchronization between the reading and writing processes
To create an anonymous pipe, the
pipe()function is used with the following function declaration:#include <unistd.h>
int pipe(int pipefd[2]);- The function parameter
pipefdis an array containing twointelements, used to return two file descriptors;pipefd[0]represents the read end, andpipefd[1]represents the write end of the pipe. After calling thepipe()function, the two processes can communicate using these two file descriptors to transfer data.
- The function parameter
Example of parent-child processes communicating through a pipe:
Step 1: The parent process calls the
pipe()function to create a pipe and obtains two file descriptors,fd[0]andfd[1], representing the read and write ends of the pipeStep 2: The parent process uses the
fork()function to create a child process, which also has two file descriptors pointing to the same pipeStep 3: The parent process closes the read end of the pipe, and the child process closes the write end. The parent process can write data to the pipe, and the child process can read data from it
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(void)
{
pid_t pid;
char buf[1024];
int fd[2];
char p[] = "test for pipe\n";
if (pipe(fd) == -1)
sys_err("pipe");
pid = fork();
if (pid < 0) {
sys_err("fork err");
}
else if (pid == 0) {
close(fd[1]);
printf("child process wait to read:\n");
int len = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
close(fd[0]);
}
else {
close(fd[0]);
write(fd[1], p, strlen(p));
wait(NULL);
close(fd[1]);
}
return 0;
}
4.2.2 Named Pipes
To create a named pipe, the
mkfifo()function is used with the following function declaration:#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);- The
pathnameparameter specifies the path of the named pipe, and themodeparameter specifies the permission mask. Callingmkfifo()function will create a named pipe at the specified path, and the return value of 0 indicates successful creation, while -1 indicates failure. - To read and write from a named pipe, you can use ordinary
read()andwrite()functions, or you can use theopen()function to open the pipe file and then useread()andwrite()functions for communication. After using the named pipe, it is necessary to use theunlink()function to delete the pipe file.
- The
In Linux, the
access()function from C language can be used to check if a process has the permission to access a file or directory. The function declaration is as follows:int access(const char *pathname, int mode);- The
pathnameparameter specifies the path of the file or directory to be checked, and themodeparameter specifies the access permissions to be checked. Common access permissions include:F_OK: Check if the file existsR_OK: Check if the file is readableW_OK: Check if the file is writableX_OK: Check if the file is executable
- The
access()function returns 0 if the check is successful and -1 if it fails. Ifaccess()function returns -1, theerrnovariable can be used to obtain the error code, which usually includes:
- The
Example of communication using named pipes:
Pipe reading end:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
int fd;
if (argc < 2)
{
printf("Usage:%s <fifo name> \n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDONLY);
while (1)
{
sleep(1);
read(fd, buf, 32);
printf("buf is %s\n", buf);
memset(buf, 0, sizeof(buf));
}
close(fd);
return 0;
}Pipe writing end:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int ret;
char buf[32] = {0};
int fd;
if (argc < 2)
{
printf("Usage:%s <fifo name> \n", argv[0]);
return -1;
}
if (access(argv[1], F_OK) == -1)
{
ret = mkfifo(argv[1], 0666);
if (ret == -1)
{
printf("mkfifo is error \n");
return -2;
}
printf("mkfifo is ok \n");
}
fd = open(argv[1], O_WRONLY);
while (1)
{
sleep(1);
write(fd, "hello", 5);
}
close(fd);
return 0;
}Compile the programs and run them:
gcc fifo_write.c -o fifow
gcc fifo_read.c -o fifor
#Open two terminals and run the programs in each terminal:
./fifor fifo
./fifow fifo
4.3 Message Queue
In the Linux system, a message queue is an inter-process communication (IPC) mechanism used for data transmission between different processes. It is a first-in-first-out (FIFO) data structure that allows one or multiple processes to communicate by sending and receiving messages in the message queue. The message queue is identified by a message queue identifier (mqid), similar to a file descriptor, which ensures the uniqueness of the message queue. When creating a message queue, a unique key needs to be specified. This key is used to identify the message queue within the system, ensuring that multiple processes can access the same message queue using the same key. Message queues enable data sharing between different processes, making them a powerful inter-process communication mechanism.
Using message queues for inter-process communication offers the following advantages:
- It supports a many-to-many communication model, where multiple processes can simultaneously read and write to the same message queue.
- Messages in the message queue can persist in the system, even if the sending process has exited. The receiving process can still read these messages.
- Message queues have a certain fault tolerance. If the receiving process is temporarily unable to process a message, the message can be retained in the queue and processed later when the receiving process is ready.
In summary, message queues are a highly practical inter-process communication mechanism widely used in various applications, including network communication, multi-threaded programming, distributed systems, and more.In the Linux system, message queue operations primarily rely on the following functions: msgget(), msgsnd(), msgrcv(), and msgctl().
4.3.1 msgget() Function
The msgget() function is used to create or retrieve a message queue. Its function prototype is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
The
keyparameter is the key value of the message queue, used to uniquely identify a message queueThe
msgflgparameter is the flag used to specify the attributes of the message queueIn the Linux system, several macros can be used to generate the
keyparameter value for themsgget()function. Commonly used macros include:IPC_PRIVATE: Represents creating a new private message queue accessible only to the current process. This flag is generally used when sharing data between parent and child processes without sharing among different processes.ftok(): Generates a unique key value based on a given file path and an integer. When usingftok(), you need to pass an accessible file path and a user-defined integer as parameters, for example:key_t key = ftok("/tmp", 'a');This code will generate a key value for the
msgget()function based on the path name/tmpand the character 'a'.IPC_CREAT: Used to create a new message queue. If the specified message queue does not exist, a new one will be created; otherwise, the function returns the identifier of the existing message queue.IPC_EXCL: Specifies that if bothIPC_CREATandIPC_EXCLflags are set, only create a new message queue if it does not already exist; otherwise, return an error.
The
msgflgparameter is used to specify the attributes of the message queue and can be set using multiple flags, including:IPC_CREAT: If the specified message queue does not exist, create a new one; otherwise, return the identifier of the existing message queue.IPC_EXCL: If bothIPC_CREATandIPC_EXCLflags are set, only create a new message queue if it does not already exist; otherwise, return an error.IPC_PRIVATE: Represents creating a new private message queue accessible only to the current process. This flag is generally used when sharing data between parent and child processes without sharing among different processes.0666: Represents the permission of the created message queue, using the same representation method as file permissions. Here, 6 represents read and write permissions, 4 represents read permissions, 2 represents write permissions, and 0 represents no permissions.
4.3.2 msgsnd() Function
The msgsnd() function is used to send a message to the message queue. Its function prototype is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
The
msqidparameter is the identifier of the message queue (obtained from themsgget()function)The
msgpparameter is a pointer to the buffer where the message to be sent is temporarily stored. A generic structure can be used to represent the message:struct msgbuf {
long mtype; /* Message type */
char mtext[1024]; /* Message content */
};The
msgszparameter is the length (in bytes) of the message to be sent. The actual length of the message (excluding the message type field) can be calculated using the formulasizeof(struct mymsg) - sizeof(long):- The total size of the
mymsgstructure issizeof(long) + 1024 = 1032bytes msgsz = sizeof(struct mymsg) - sizeof(long) = 1032 - 8 = 1024bytes.
- The total size of the
The
msgflgparameter is used to specify the behavior of sending the message and can take the following values:0: Represents the blocking mode, where the thread will be blocked until the message can be writtenIPC_NOWAIT: Represents the non-blocking mode, where the function immediately returns with an error if the message queue is full or other conditions prevent sending the message
If the function executes successfully, it returns
0; otherwise, it returns-1.
4.3.3 msgrcv() Function
The msgrcv() function is used to receive a message from the message queue. Its function prototype is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
The
msqidparameter is the identifier of the message queueThe
msgpparameter is a pointer to the buffer where the received message will be stored. A generic structure can be used to represent the message:struct msgbuf {
long mtype; /* Message type */
char mtext[1024]; /* Message content */
};The
msgszparameter is the length of the received messageThe
msgtypparameter specifies the type of the message to receiveThe
msgflgparameter is used to specify the behavior of receiving the message and can take the following values:0: Represents the blocking mode, where the function waits until the message queue is not emptyIPC_NOWAIT: Represents the non-blocking mode, where the function immediately returns with an error if the message queue is empty
If the function executes successfully, it returns the number of bytes received into the
mtextarray.
4.3.3 msgctl() Function
The msgctl() function is used to control the state of the message queue. Its function prototype is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- The
msqidparameter is the identifier of the message queue. - The
cmdparameter specifies the operation to be executed, including deleting the message queue, querying the status of the message queue, etc. It can take the following values:IPC_STAT: Read the attributes of the message queue and save them in the buffer pointed to bybufIPC_SET: Set the attributes of the message queue based on the values from thebufparameterIPC_EMID: Delete the queue from the system kernel
- The
bufparameter is a pointer to themsqid_dsstructure used to save the status information of the message queue.
Below is a simple example of a message queue program, including sender and receiver processes:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_KEY 1234
/* Define the message structure */
struct mymsg {
long mtype; /* Message type */
char mtext[1024]; /* Message content */
};
/* Sender process */
void sender()
{
int msgid, ret;
struct mymsg msg;
/* Create or open the message queue */
msgid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msgid < 0) {
perror("msgget");
exit(1);
}
/* Construct the message */
msg.mtype = 1;
strncpy(msg.mtext, "Hello, waveshare!", 1024);
/* Send the message */
ret = msgsnd(msgid, &msg, sizeof(struct mymsg) - sizeof(long), 0);
if (ret < 0) {
perror("msgsnd");
exit(1);
}
printf("Sent message: %s\n", msg.mtext);
}
/* Receiver process */
void receiver()
{
int msgid, ret;
struct mymsg msg;
/* Open the message queue */
msgid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msgid < 0) {
perror("msgget");
exit(1);
}
/* Receive the message */
ret = msgrcv(msgid, &msg, sizeof(struct mymsg) - sizeof(long), 1, 0);
if (ret < 0) {
perror("msgrcv");
exit(1);
}
printf("Received message: %s\n", msg.mtext);
}
int main()
{
pid_t pid;
/* Create a child process */
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
} else if (pid == 0) {
/* Child process acts as the sender */
sender();
} else {
/* Parent process acts as the receiver */
receiver();
}
return 0;
}
4.4 System-V IPC Semaphores
System V Semaphores are a mechanism for inter-process synchronization and mutual exclusion. They use a counter-based approach to control access to shared resources, allowing multiple processes to access the resource simultaneously while limiting the number of concurrent accesses. This mechanism ensures that each process can smoothly acquire control of the resource, achieving synchronization and mutual exclusion between multiple processes.
4.4.1 Creating or Getting a Semaphore
In System V IPC (Inter-Process Communication), use the semget() function to create or get a semaphore set. The function declaration is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
Parameter explanation:
key: The key value that identifies the semaphore set. You can use theftokfunction to generate a unique key valuensems: Specifies the number of semaphores in the semaphore set to be created or obtainedsemflg: Specifies the operation flags, which can be one or a combination of several flags
Function return value: The function returns a non-negative integer if successful, representing the semaphore set identifier (also known as the semaphore set descriptor). If the function fails, it returns -1.
4.4.2 Operating on Semaphores
In System V IPC, use the semop() function to perform P/V operations (increment and decrement operations) on semaphores. The function declaration is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
Parameter explanation:
semid: The semaphore set identifier (returned by thesemgetfunction)sops: A pointer to an array of structures, with each structure describing a P/V operationnsops: Specifies the number of structures in thesopsarray
Function return value: The function returns 0 if successful, and -1 if it fails
The
sembufstructure is defined as follows:struct sembuf {
unsigned short sem_num; // The number of the semaphore to operate on
short sem_op; // Operation flag: 1 for V (release resource), -1 for P (allocate resource), 0 for wait until the semaphore value becomes 0
short sem_flg; // 0 for blocking, IPC_NOWAIT for non-blocking
};There are two basic ways to operate on semaphores:
- P operation, also known as the wait operation or decrement operation, is used to acquire control of the semaphore and decrement its value. Specifically, when a process needs to access a shared resource, it calls the P operation to wait for the semaphore. If the semaphore's value is greater than or equal to 1, the process will decrement the semaphore's value and continue execution. Otherwise, the process will be blocked and wait until the semaphore's value becomes greater than or equal to 1
- V operation, also known as the signal operation or increment operation, is used to release control of the semaphore and increment its value. Specifically, when a process finishes accessing a shared resource, it calls the V operation to release the semaphore. This allows other processes waiting for the semaphore to regain control and continue execution
- PV operations are the most basic and commonly used operations in System V Semaphores. They can be used to implement various synchronization and mutual exclusion mechanisms, such as mutexes and read-write locks. Due to the atomic nature of PV operations, they ensure that access to shared resources does not lead to race conditions and other issues, thus ensuring synchronization and mutual exclusion between multiple processes
4.4.3 Controlling Semaphore Set Attributes
In System V IPC, use the semctl() function to control semaphore set attributes. The function declaration is as follows:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
Parameter explanation:
semid: The semaphore set identifier (returned by thesemgetfunction)semnum: The index of the semaphore in the semaphore set. For most commands, this parameter is ignored and can be set to 0cmd: Specifies the operation command to be executed, which can be one of the following commands:GETVAL: Get the current value of the semaphoreSETVAL: Set the value of the semaphoreSETALL: Set the values of all semaphores in the setIPC_RMID: Remove the semaphore set
Function return value: The function returns a non-negative integer if successful and -1 if it fails.
When using the
semctlfunction, depending on the command, additional parameters may be required. For example, for theSETVALcommand, you need to provide aunion semunstructure as the fourth parameter, which contains the value to be set. Theunion semunstructure is defined as follows:union semun {
int val; // Value used for SETVAL command
struct semid_ds *buf; // Buffer used for IPC_STAT and IPC_SET commands
unsigned short *array; // Array used for GETALL and SETALL commands
};
4.4.4 Semaphore Example
Using semaphores to control the execution order between two processes (parent and child):
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semun
{
int val;
};
int main(void)
{
int semid;
int key;
pid_t pid;
struct sembuf sem;
union semun semun_union;
key = ftok("./a.c", 0666);
semid = semget(key, 1, 0666 | IPC_CREAT);
semun_union.val = 0;
semctl(semid, 0, SETVAL, semun_union);
pid = fork();
if (pid > 0)
{
sem.sem_num = 0;
sem.sem_op = -1; // P operation
sem.sem_flg = 0;
semop(semid, &sem, 1);
printf("This is parents\n");
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = 0;
semop(semid, &sem, 1);
}
if (pid == 0)
{
sleep(2);
sem.sem_num = 0;
sem.sem_op = 1; //V operation
sem.sem_flg = 0;
semop(semid, &sem, 1);
printf("This is son\n");
}
return 0;
}
Running the program:
linaro@linaro-alip:~/Linux/process$ ./semget
This is parents
This is son
linaro@linaro-alip:~/Linux/process$
4.5 Shared Memory
Shared memory refers to a mechanism where multiple processes share the same physical memory. This mechanism allows multiple processes to access the same memory area, enabling data sharing between processes.
Under the shared memory mechanism, multiple processes can map the same memory region to their address space to achieve data sharing. This allows multiple processes to read and write the same memory, achieving communication and synchronization between them without the need for any data copying or inter-process communication operations. Therefore, shared memory is an efficient inter-process communication mechanism.
4.5.1 Creating New Shared Memory
In Linux, use the shmget() function to create shared memory. The function declaration is as follows:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
In the function,
keyspecifies the key value for identifying the shared memory,sizespecifies the size of the shared memory, andshmflgspecifies the creation flags, which can be one or a combination of the following constants:IPC_CREAT: If the shared memory does not exist, create it; otherwise, return the identifier of the existing shared memoryIPC_EXCL: Used together withIPC_CREAT, return an error if the shared memory already existsIPC_PRIVATE: Use a random value as the key to create shared memory
Function return value: The function returns an integer indicating the identifier of the shared memory segment. If the creation fails, the function returns -1 and sets the
errnovariable to indicate the error type
Below is an example code that demonstrates how to use the shmget() function to create shared memory:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int shmid;
shmid = shmget(IPC_PRIVATE,1024,0777);
if(shmid < 0)
{
printf("shmget is error n");
return -1;
}
printf("Shared memory segment ID = %d\n", shmid);
exit(0);
}
Running the program:
linaro@linaro-alip:~/Linux/process$ gcc -o shmget shmget.c
linaro@linaro-alip:~/Linux/process$ ./shmget
Shared memory segment ID = 983045
linaro@linaro-alip:~/Linux/process$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 65536 linaro 600 33554432 2 dest
0x00000000 163841 linaro 600 2097152 2 dest
0x00000000 196610 linaro 600 393216 2 dest
0x00000000 950275 linaro 600 524288 2 dest
0x00000000 491524 linaro 600 524288 2 dest
0x00000000 983045 linaro 777 1024 0
4.5.2 Mapping Shared Memory with shmat()
The shmat() function is used in Linux to map shared memory to the process's address space. The function declaration is as follows:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- In the function,
shmidis the identifier of the shared memory, which is returned by theshmgetfunction - The
shmaddrparameter is used to specify the starting address of the mapping. If the value ofshmaddris 0, the operating system will choose a suitable address for mapping, usually written asNULL - The
shmflgparameter is used to specify the mapping flags, and0is typically used to indicate default options for operating on shared memory:SHM_RDONLY: Map the shared memory region as read-onlySHM_REMAP: Remap the shared memory into the process's address spaceSHM_EXEC: Allow the shared memory content to be executable. Generally, the content of shared memory is not allowed to be executedSHM_DEST: When theshmdt()function is called to detach shared memory, it will be destroyed
- Function return value: The
shmat()function returns a pointer that points to the starting address of the shared memory mapping. If the mapping fails, the function returns -1 and sets theerrnovariable to indicate the error type.
4.5.3 Detaching Shared Memory with shmdt()
The shmdt() function is used in Linux to detach shared memory from the process's address space. The function declaration is as follows:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
- The
shmaddrparameter is the starting address of the shared memory mapping, usually obtained from the pointer returned by theshmat()function - Function return value: The function returns 0 on success and -1 on failure
- Note: The
shmdt()function detaches the shared memory from the process's address space but does not delete the shared memory region. It just makes the current process unable to access the shared memory.
4.5.4 Controlling Shared Memory Attributes with shmctl()
The shmctl() function in Linux is used to control shared memory. It can be used to get or modify shared memory attributes. The function declaration is as follows:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
The
shmidparameter is the identifier of the shared memory, which is returned by theshmgetfunction. Thecmdparameter specifies the operation command to be executed. It can be one of the following commonly used commands:IPC_STAT: Get the status information of the shared memory and store the result in theshmid_dsstructure pointed to bybufIPC_SET: Set the status information of the shared memory using the data in thebufstructureIPC_RMID: Remove the shared memory and release its resources
The
bufparameter is a pointer to astruct shmid_dsstructure used to store or pass the status information of the shared memoryFunction return value: The function returns 0 on success and -1 on failure.
4.5.5 Shared Memory Application Example
Creating shared memory between parent and child processes:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int shmid;
key_t key;
pid_t pid;
char *s_addr, *p_addr;
key = ftok("./a.c", 'a');
shmid = shmget(key, 1024, 0777 | IPC_CREAT);
if (shmid < 0)
{
printf("shmget is error\n");
return -1;
}
printf("shmget is ok and shmid is %d\n", shmid);
pid = fork();
if (pid > 0)
{
p_addr = shmat(shmid, NULL, 0);
strncpy(p_addr, "hello", 5);
wait(NULL);
exit(0);
}
if (pid == 0)
{
sleep(2);
s_addr = shmat(shmid, NULL, 0);
printf("s_addr is %s\n", s_addr);
exit(0);
}
return 0;
}