Linux应用:进程间通信

linux的进程间通信概述

进程间通信(IPC,Inter - Process Communication)是指在不同进程之间进行数据交换和同步的机制。由于每个进程都有自己独立的地址空间,直接共享内存存在困难,因此需要专门的 IPC 机制来实现进程之间的交互。常见的 IPC 机制有管道、消息队列、共享内存、信号量、套接字等,它们各自适用于不同的场景,具有不同的优缺点。

linux的IPC机制-管道

管道是一种最基本的 IPC 机制,它用于在两个进程之间单向传输数据。管道分为匿名管道和命名管道。匿名管道只能用于具有亲缘关系(如父子进程)的进程之间通信,它通过pipe函数创建。命名管道(FIFO)则可以用于任意两个进程之间通信,它通过mkfifo函数创建。在使用管道时,一个进程向管道写入数据,另一个进程从管道读取数据,数据按照先进先出(FIFO)的顺序传输。

匿名管道

概念
匿名管道是一种半双工的通信方式,即数据只能在一个方向上流动,**通常用于具有亲缘关系的进程(如父子进程)之间进行通信。**它没有实际的文件系统实体与之关联,只能在创建它的进程及其子进程中使用。
工作原理
当一个进程调用pipe()系统调用时,操作系统会创建一个匿名管道,并返回两个文件描述符,一个用于读取(fd[0]),另一个用于写入(fd[1])。
进程可以通过write()系统调用向写端文件描述符写入数据,而另一个进程可以通过read()系统调用从读端文件描述符读取数据。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#define BUFFER_SIZE 1024int main() {int pipe_fd[2];pid_t child_pid;char buffer[BUFFER_SIZE];// 创建匿名管道if (pipe(pipe_fd) == -1) {perror("pipe");return EXIT_FAILURE;}// 创建子进程child_pid = fork();if (child_pid == -1) {perror("fork");return EXIT_FAILURE;} else if (child_pid == 0) {  // 子进程close(pipe_fd[1]);  // 关闭写端ssize_t bytes_read = read(pipe_fd[0], buffer, BUFFER_SIZE - 1);if (bytes_read == -1) {perror("read");return EXIT_FAILURE;}buffer[bytes_read] = '\0';printf("Child process received: %s\n", buffer);close(pipe_fd[0]);  // 关闭读端} else {  // 父进程close(pipe_fd[0]);  // 关闭读端const char *message = "Hello from parent!";ssize_t bytes_written = write(pipe_fd[1], message, strlen(message));if (bytes_written == -1) {perror("write");return EXIT_FAILURE;}close(pipe_fd[1]);  // 关闭写端}return EXIT_SUCCESS;
}

stdio.h:提供标准输入输出函数,像printf这类函数就出自该头文件。
stdlib.h:包含了一些常用的函数,例如EXIT_SUCCESS和EXIT_FAILURE等常量的定义。
unistd.h:提供了许多 Unix 标准的系统调用,像pipe、fork、read、write和close这些函数就在其中。
string.h:提供字符串处理函数,例如strlen。
定义了一个常量BUFFER_SIZE,其值为 1024,此常量用于指定缓冲区的大小。
pipe_fd[2]:这是一个包含两个元素的整型数组,用于存储管道的文件描述符。pipe_fd[0]是读端,pipe_fd[1]是写端。
child_pid:用于存储fork函数的返回值,以此判断当前是父进程还是子进程。
buffer:用于存储从管道读取的数据。
pipe(pipe_fd):调用pipe函数创建一个匿名管道,若创建成功,pipe_fd[0]会被初始化为读端的文件描述符,pipe_fd[1]会被初始化为写端的文件描述符。
若创建失败,pipe函数会返回 -1,此时调用perror(“pipe”)输出错误信息,并且程序以失败状态退出。
fork():调用fork函数创建一个新的子进程。此函数在父进程中返回子进程的进程 ID,在子进程中返回 0,若创建失败则返回 -1。
若fork失败,perror(“fork”)会输出错误信息,程序以失败状态退出。
若child_pid为 0,表明当前是子进程。
close(pipe_fd[1]):子进程不需要写管道,所以关闭写端。
read(pipe_fd[0], buffer, BUFFER_SIZE - 1):从管道的读端读取数据,将其存储到buffer中,最多读取BUFFER_SIZE - 1字节。read函数返回实际读取的字节数。
若read失败,perror(“read”)会输出错误信息,程序以失败状态退出。
buffer[bytes_read] = ‘\0’:在读取的数据末尾添加字符串结束符,把buffer转换为一个以\0结尾的字符串。
printf(“Child process received: %s\n”, buffer):输出从管道读取的数据。
close(pipe_fd[0]):读取完成后,关闭管道的读端。
通过匿名管道实现了父子进程间的通信。父进程向管道写入消息,子进程从管道读取消息并输出。
在这里插入图片描述

命名管道

概念
命名管道是一种在文件系统中具有实际名称的管道,**它可以在不具有亲缘关系的进程之间进行通信。**命名管道以文件的形式存在于文件系统中,但它实际上是一种特殊的文件,用于在进程间传递数据。
工作原理
进程可以通过mkfifo()系统调用创建一个命名管道,该管道会在文件系统中创建一个特殊的文件。
其他进程可以通过open()系统调用打开这个命名管道文件,进行读写操作,就像操作普通文件一样。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>#define FIFO_NAME "my_fifo"
#define BUFFER_SIZE 1024int main() {int fd;char buffer[BUFFER_SIZE];const char *message = "Hello from writer!";// 创建命名管道if (mkfifo(FIFO_NAME, 0666) == -1 && errno != EEXIST) {perror("mkfifo");return EXIT_FAILURE;}// 打开命名管道进行写操作fd = open(FIFO_NAME, O_WRONLY);if (fd == -1) {perror("open");return EXIT_FAILURE;}ssize_t bytes_written = write(fd, message, strlen(message));if (bytes_written == -1) {perror("write");return EXIT_FAILURE;}close(fd);return EXIT_SUCCESS;
}

stdio.h:提供标准输入输出函数,例如printf、perror等。
stdlib.h:包含标准库函数,这里用到了EXIT_SUCCESS和EXIT_FAILURE两个宏,用于表示程序的退出状态。
fcntl.h:提供文件控制相关的函数和常量,像open函数以及O_WRONLY这样的标志就定义在其中。
unistd.h:提供 Unix 标准的系统调用,例如mkfifo、write、close等。
string.h:提供字符串处理函数,这里使用了strlen来计算字符串的长度。
FIFO_NAME:定义了命名管道的名称,程序会使用这个名称来创建和操作命名管道。
BUFFER_SIZE:定义了缓冲区的大小,不过在这段代码中,该缓冲区未被实际使用。
fd:文件描述符,用于标识打开的命名管道,后续对管道的读写操作会用到它。
buffer:大小为BUFFER_SIZE的字符数组,不过在当前代码中没有被使用。
message:指向一个常量字符串的指针,该字符串是要写入命名管道的消息内容。
mkfifo(FIFO_NAME, 0666):调用mkfifo函数创建一个命名管道,FIFO_NAME是管道的名称,0666是管道的权限,表示所有用户都有读写权限。
errno != EEXIST:如果mkfifo返回 -1 且errno的值为EEXIST,说明命名管道已经存在,这不是一个错误情况;只有当errno不是EEXIST时,才表明创建管道出现了真正的错误。
perror(“mkfifo”):若创建管道失败,使用perror函数输出错误信息。
return EXIT_FAILURE:程序以失败状态退出。
open(FIFO_NAME, O_WRONLY):调用open函数以只写模式打开命名管道,O_WRONLY表示只写。
fd == -1:若open返回 -1,说明打开管道失败,使用perror函数输出错误信息,程序以失败状态退出。

write(fd, message, strlen(message)):调用write函数将message指向的字符串写入到命名管道中,strlen(message)是要写入的字节数。
bytes_written == -1:若write返回 -1,说明写入操作失败,使用perror函数输出错误信息,程序以失败状态退出。

close(fd):调用close函数关闭命名管道。
return EXIT_SUCCESS:程序以成功状态退出。

读端

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>#define FIFO_NAME "my_fifo"
#define BUFFER_SIZE 1024int main() {int fd;char buffer[BUFFER_SIZE];// 打开命名管道进行读操作fd = open(FIFO_NAME, O_RDONLY);if (fd == -1) {perror("open");return EXIT_FAILURE;}ssize_t bytes_read = read(fd, buffer, BUFFER_SIZE - 1);if (bytes_read == -1) {perror("read");return EXIT_FAILURE;}buffer[bytes_read] = '\0';printf("Reader received: %s\n", buffer);close(fd);return EXIT_SUCCESS;
}

SystemV IPC介绍

SystemV IPC 是 UNIX System V 操作系统中引入的一组进程间通信机制,包括消息队列、共享内存和信号量。这些机制在 Linux 系统中也得到了广泛应用。​

消息队列

消息队列:消息队列是一个消息的链表,进程可以向消息队列发送消息,也可以从消息队列接收消息。消息队列中的消息具有特定的类型,接收进程可以根据消息类型选择性地接收消息。​
发送端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define MSG_SIZE 128
#define MSG_TYPE 1typedef struct {long mtype;char mtext[MSG_SIZE];
} Message;int main() {key_t key;int msgid;Message message;// 获取键值key = ftok(".", 'a');if (key == -1) {perror("ftok");return EXIT_FAILURE;}// 创建消息队列msgid = msgget(key, IPC_CREAT | 0666);if (msgid == -1) {perror("msgget");return EXIT_FAILURE;}message.mtype = MSG_TYPE;strcpy(message.mtext, "Hello from sender!");// 发送消息if (msgsnd(msgid, &message, strlen(message.mtext) + 1, 0) == -1) {perror("msgsnd");return EXIT_FAILURE;}return EXIT_SUCCESS;
}

stdio.h:提供标准输入输出函数,例如printf、perror。
stdlib.h:包含标准库函数,如EXIT_SUCCESS和EXIT_FAILURE,用于表示程序的退出状态。
sys/types.h:定义了一些系统数据类型,如key_t、size_t等。
sys/ipc.h:提供了与进程间通信(IPC)相关的函数和常量,如ftok函数。
sys/msg.h:提供了消息队列相关的函数和常量,如msgget、msgsnd等。
string.h:提供字符串处理函数,如strcpy、strlen。
MSG_SIZE:定义了消息文本的最大长度,即Message结构体中mtext数组的大小。
MSG_TYPE:定义了消息的类型,在消息队列中,不同类型的消息可以被不同的接收端有选择地接收。

定义了一个名为Message的结构体,用于表示消息队列中的消息。
mtype:消息的类型,是一个长整型,用于区分不同类型的消息。
mtext:消息的文本内容,是一个字符数组,大小为MSG_SIZE。

key:用于唯一标识一个消息队列,是ftok函数生成的键值。
msgid:消息队列的标识符,是msgget函数返回的消息队列 ID。
message:Message结构体类型的变量,用于存储要发送的消息。

ftok函数用于生成一个唯一的键值,该键值将用于标识消息队列。
.:表示当前工作目录,作为生成键值的路径。
‘a’:是一个项目 ID,与路径一起生成唯一的键值。
如果ftok函数返回 -1,表示生成键值失败,使用perror输出错误信息并以失败状态退出程序。
msgget函数用于创建或获取一个消息队列。
key:是ftok生成的键值,用于标识消息队列。
IPC_CREAT | 0666:IPC_CREAT表示如果消息队列不存在则创建它,0666是消息队列的权限,表示所有用户都有读写权限。
如果msgget函数返回 -1,表示创建或获取消息队列失败,使用perror输出错误信息并以失败状态退出程序。
将消息的类型设置为MSG_TYPE,即之前定义的消息类型。
strcpy:使用strcpy函数将字符串"Hello from sender!"复制到message.mtext中。
msgsnd函数用于将消息发送到消息队列中。
msgid:是消息队列的标识符。
&message:是要发送的消息的指针。
strlen(message.mtext) + 1:是要发送的消息的长度,包括字符串结束符’\0’。
0:表示如果消息队列已满则阻塞等待。
如果msgsnd函数返回 -1,表示发送消息失败,使用perror输出错误信息并以失败状态退出程序。
代码实现了通过消息队列发送消息的功能,包括生成键值、创建消息队列、填充消息内容并发送消息。

接收端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define MSG_SIZE 128
#define MSG_TYPE 1typedef struct {long mtype;char mtext[MSG_SIZE];
} Message;int main() {key_t key;int msgid;Message message;// 获取键值key = ftok(".", 'a');if (key == -1) {perror("ftok");return EXIT_FAILURE;}// 获取消息队列msgid = msgget(key, 0);if (msgid == -1) {perror("msgget");return EXIT_FAILURE;}// 接收消息if (msgrcv(msgid, &message, MSG_SIZE, MSG_TYPE, 0) == -1) {perror("msgrcv");return EXIT_FAILURE;}printf("Receiver received: %s\n", message.mtext);return EXIT_SUCCESS;
}

key:类型为key_t,用于存储通过ftok函数生成的唯一键值,该键值用于标识消息队列。
msgid:是一个整数,用于存储消息队列的标识符,后续接收消息的操作会用到它。
message:是Message结构体类型的变量,此结构体在之前的代码中应该已经定义,用来存储从消息队列接收的消息。
ftok函数的作用是根据给定的路径名和项目 ID 生成一个系统范围内唯一的键值。
.:代表当前工作目录,作为生成键值的路径依据。
‘a’:是项目 ID,与路径共同生成唯一的键值。
若ftok函数返回 -1,意味着键值生成失败。此时调用perror(“ftok”)输出错误信息,程序以失败状态退出。

msgget函数用于获取一个已经存在的消息队列。
key:是前面通过ftok生成的键值,用于定位消息队列。
0:表示不创建新的消息队列,仅尝试获取已存在的消息队列。
若msgget函数返回 -1,说明获取消息队列失败。调用perror(“msgget”)输出错误信息,程序以失败状态退出。
msgrcv函数用于从消息队列中接收消息。
msgid:消息队列的标识符,指定从哪个消息队列接收消息。
&message:指向Message结构体变量的指针,用于存储接收到的消息。
MSG_SIZE:指定接收消息时mtext字段的最大长度,防止接收数据时发生缓冲区溢出。
MSG_TYPE:指定要接收的消息类型,只有消息类型匹配的消息才会被接收。
0:表示若消息队列中没有指定类型的消息,函数会阻塞等待,直到有符合条件的消息到来。
若msgrcv函数返回 -1,表明接收消息失败。调用perror(“msgrcv”)输出错误信息,程序以失败状态退出。
通过消息队列接收指定类型的消息,并将接收到的消息内容输出。它依赖于之前发送端程序创建的消息队列和发送的消息。

共享内存

共享内存:共享内存允许多个进程共享同一块内存区域,这是一种高效的 IPC 机制,因为进程可以直接读写共享内存,而无需像管道那样进行数据复制。但由于多个进程同时访问共享内存可能会导致竞态条件,需要结合信号量等同步机制来保证数据的一致性。​

写入端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>#define SHM_SIZE 1024int main() {key_t key;int shmid;char *shm_ptr;// 获取键值key = ftok(".", 'a');if (key == -1) {perror("ftok");return EXIT_FAILURE;}// 创建共享内存段shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");return EXIT_FAILURE;}// 连接共享内存段到进程地址空间shm_ptr = shmat(shmid, NULL, 0);if (shm_ptr == (void *)-1) {perror("shmat");return EXIT_FAILURE;}const char *message = "Hello from writer to shared memory!";strcpy(shm_ptr, message);// 分离共享内存段if (shmdt(shm_ptr) == -1) {perror("shmdt");return EXIT_FAILURE;}return EXIT_SUCCESS;
}

stdio.h:提供标准输入输出函数,如 printf 和 perror。
stdlib.h:包含标准库函数和常量,如 EXIT_SUCCESS 和 EXIT_FAILURE。
sys/types.h:定义了一些系统数据类型,如 key_t。
sys/ipc.h:提供进程间通信(IPC)相关的函数和常量,如 ftok。
sys/shm.h:提供共享内存操作的函数和常量,如 shmget、shmat 和 shmdt。
string.h:提供字符串处理函数,如 strcpy。
key:类型为 key_t,用于存储通过 ftok 函数生成的唯一键值,该键值用于标识共享内存段。
shmid:整数类型,用于存储共享内存段的标识符,后续的共享内存操作会用到它。
shm_ptr:字符指针,用于指向连接到进程地址空间后的共享内存段。

ftok 函数根据指定的路径名(. 表示当前工作目录)和项目 ID(‘a’)生成一个系统范围内唯一的键值。
如果 ftok 函数返回 -1,表示生成键值失败,使用 perror 输出错误信息并以失败状态退出程序。
shmget 函数用于创建或获取一个共享内存段。
key:是前面生成的键值,用于标识共享内存段。
SHM_SIZE:指定共享内存段的大小。
IPC_CREAT | 0666:IPC_CREAT 表示如果共享内存段不存在则创建它,0666 是共享内存段的权限,表示所有用户都有读写权限。
如果 shmget 函数返回 -1,表示创建或获取共享内存段失败,使用 perror 输出错误信息并以失败状态退出程序。
shmat 函数将指定的共享内存段连接到当前进程的地址空间。
shmid:是共享内存段的标识符。
NULL:表示让系统自动选择一个合适的地址来连接共享内存段。
0:表示以默认的读写权限连接共享内存段。
如果 shmat 函数返回 (void *)-1,表示连接共享内存段失败,使用 perror 输出错误信息并以失败状态退出程序。
shmdt 函数将共享内存段从当前进程的地址空间中分离。
如果 shmdt 函数返回 -1,表示分离共享内存段失败,使用 perror 输出错误信息并以失败状态退出程序。
创建共享内存段、连接到该共享内存段、向其中写入数据,最后分离共享内存段的功能。其他进程可以通过相同的键值连接到这个共享内存段并读取其中的数据,从而实现进程间的数据共享。

读取端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>#define SHM_SIZE 1024int main() {key_t key;int shmid;char *shm_ptr;// 获取键值key = ftok(".", 'a');if (key == -1) {perror("ftok");return EXIT_FAILURE;}// 获取共享内存段shmid = shmget(key, SHM_SIZE, 0);if (shmid == -1) {perror("shmget");return EXIT_FAILURE;}// 连接共享内存段到进程地址空间shm_ptr = shmat(shmid, NULL, 0);if (shm_ptr == (void *)-1) {perror("shmat");return EXIT_FAILURE;}printf("Reader read from shared memory: %s\n", shm_ptr);// 分离共享内存段if (shmdt(shm_ptr) == -1) {perror("shmdt");return EXIT_FAILURE;}return EXIT_SUCCESS;
}

key:类型为key_t,用于存储由ftok函数生成的唯一键值,这个键值用于标识共享内存段。
shmid:整数类型,代表共享内存段的标识符,后续的共享内存操作会用到它。
shm_ptr:字符指针,指向连接到进程地址空间后的共享内存段,方便对共享内存中的数据进行读写操作。
ftok函数会根据指定的路径名(. 表示当前工作目录)和项目 ID(‘a’)生成一个系统范围内唯一的键值。这个键值是后续定位共享内存段的关键。
若ftok函数返回 -1,意味着键值生成失败。此时调用perror(“ftok”)输出错误信息,程序以失败状态退出。
shmget函数的作用是获取一个已经存在的共享内存段。
key:就是前面通过ftok函数生成的键值,用于定位共享内存段。
SHM_SIZE:指定共享内存段的大小,这里应该是之前创建共享内存段时使用的大小。
0:表示不创建新的共享内存段,仅尝试获取已存在的共享内存段。
若shmget函数返回 -1,说明获取共享内存段失败。调用perror(“shmget”)输出错误信息,程序以失败状态退出。
shmat函数将指定的共享内存段连接到当前进程的地址空间。
shmid:共享内存段的标识符。
NULL:表示让系统自动选择一个合适的地址来连接共享内存段。
0:表示以默认的读写权限连接共享内存段。
若shmat函数返回(void *)-1,意味着连接共享内存段失败。调用perror(“shmat”)输出错误信息,程序以失败状态退出。

shmdt函数的作用是将共享内存段从当前进程的地址空间中分离。
若shmdt函数返回 -1,表明分离共享内存段失败。调用perror(“shmdt”)输出错误信息,程序以失败状态退出。

代码实现了连接到已存在的共享内存段、读取其中的数据、输出数据,最后分离共享内存段的功能,与之前创建并写入共享内存段的程序配合,实现了进程间的数据共享。

信号量

信号量是一种用于实现进程同步和互斥的工具,它本质上是一个计数器。通过对信号量的操作(如 P 操作和 V 操作),进程可以控制对共享资源的访问,避免多个进程同时访问共享资源导致的数据错误。​
在 Linux 中,信号量操作主要通过semget、semop和semctl等函数来实现。semget用于创建或获取信号量集,semop用于执行信号量操作(P 操作和 V 操作),semctl用于控制信号量(如初始化信号量的值等)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <sys/shm.h>
#define SEM_KEY 1234
#define SHM_KEY 5678
#define SHM_SIZE 1024union semun {int val;struct semid_ds *buf;unsigned short *array;
};int main() {key_t key;int semid, shmid;char *shm_ptr;struct sembuf sem_ops[1];// 获取信号量集semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);if (semid == -1) {perror("semget");return EXIT_FAILURE;}// 初始化信号量的值为1union semun arg;arg.val = 1;if (semctl(semid, 0, SETVAL, arg) == -1) {perror("semctl");return EXIT_FAILURE;}// 获取共享内存段shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");return EXIT_FAILURE;}// 连接共享内存段到进程地址空间shm_ptr = static_cast<char*>(shmat(shmid, NULL, 0));if (shm_ptr == (void *)-1) {perror("shmat");return EXIT_FAILURE;}// P操作(等待信号量)sem_ops[0].sem_num = 0;sem_ops[0].sem_op = -1;sem_ops[0].sem_flg = SEM_UNDO;if (semop(semid, sem_ops, 1) == -1) {perror("semop");return EXIT_FAILURE;}// 访问共享资源(这里简单地写入数据到共享内存)const char *message = "Accessed by process";strcpy(shm_ptr, message);// V操作(释放信号量)sem_ops[0].sem_op = 1;if (semop(semid, sem_ops, 1) == -1) {perror("semop");return EXIT_FAILURE;}// 分离共享内存段if (shmdt(shm_ptr) == -1) {perror("shmdt");return EXIT_FAILURE;}return EXIT_SUCCESS;
}

stdio.h:提供标准输入输出函数,如 printf 和 perror。
stdlib.h:包含标准库函数和常量,如 EXIT_SUCCESS 和 EXIT_FAILURE。
sys/types.h:定义了一些系统数据类型,如 key_t。
sys/ipc.h:提供进程间通信(IPC)相关的函数和常量,如 ftok。
sys/sem.h:提供信号量操作的函数和常量,如 semget、semctl 和 semop。
unistd.h:提供了一些 Unix 标准的系统调用和函数。
SEM_KEY:定义信号量集的键值,用于标识信号量集。
SHM_KEY:定义共享内存段的键值,用于标识共享内存段。
SHM_SIZE:定义共享内存段的大小为 1024 字节。

semun 联合体用于 semctl 函数的第四个参数,不同的操作需要使用联合体中的不同成员。
key:类型为 key_t,虽然这里定义了但未使用。
semid:整数类型,用于存储信号量集的标识符。
shmid:整数类型,用于存储共享内存段的标识符。
shm_ptr:字符指针,用于指向连接到进程地址空间后的共享内存段。
sem_ops:struct sembuf 类型的数组,用于存储信号量操作的相关信息。
semget 函数用于创建或获取一个信号量集。
SEM_KEY:是信号量集的键值。
1:表示信号量集中信号量的数量为 1。
IPC_CREAT | 0666:IPC_CREAT 表示如果信号量集不存在则创建它,0666 是信号量集的权限,表示所有用户都有读写权限。
如果 semget 函数返回 -1,表示创建或获取信号量集失败,使用 perror 输出错误信息并以失败状态退出程序。
semctl 函数用于控制信号量集。
semid:是信号量集的标识符。
0:表示要操作的信号量在信号量集中的索引,这里只有一个信号量,所以索引为 0。
SETVAL:表示要执行的操作是设置信号量的值。
arg:semun 联合体,arg.val = 1 表示将信号量的值设置为 1。
如果 semctl 函数返回 -1,表示设置信号量值失败,使用 perror 输出错误信息并以失败状态退出程序。
hmget 函数用于创建或获取一个共享内存段。
SHM_KEY:是共享内存段的键值。
SHM_SIZE:是共享内存段的大小。
IPC_CREAT | 0666:IPC_CREAT 表示如果共享内存段不存在则创建它,0666 是共享内存段的权限,表示所有用户都有读写权限。
如果 shmget 函数返回 -1,表示创建或获取共享内存段失败,使用 perror 输出错误信息并以失败状态退出程序。
shmat 函数将指定的共享内存段连接到当前进程的地址空间。
shmid:是共享内存段的标识符。
NULL:表示让系统自动选择一个合适的地址来连接共享内存段。
0:表示以默认的读写权限连接共享内存段。
如果 shmat 函数返回 (void *)-1,表示连接共享内存段失败,使用 perror 输出错误信息并以失败状态退出程序。
semop 函数用于对信号量集执行操作。
sem_ops[0].sem_num = 0:表示要操作的信号量在信号量集中的索引为 0。
sem_ops[0].sem_op = -1:表示将信号量的值减 1,如果信号量的值小于 0,则进程会阻塞等待。
sem_ops[0].sem_flg = SEM_UNDO:表示如果进程异常终止,系统会自动恢复信号量的值。
如果 semop 函数返回 -1,表示信号量操作失败,使用 perror 输出错误信息并以失败状态退出程序。
定义一个常量字符串 message。
使用 strcpy 函数将字符串 message 复制到共享内存段(由 shm_ptr 指向)中。

sem_ops[0].sem_op = 1:表示将信号量的值加 1,唤醒可能正在等待该信号量的进程。
如果 semop 函数返回 -1,表示信号量操作失败,使用 perror 输出错误信息并以失败状态退出程序。

shmdt 函数将共享内存段从当前进程的地址空间中分离。
如果 shmdt 函数返回 -1,表示分离共享内存段失败,使用 perror 输出错误信息并以失败状态退出程序。
代码通过信号量机制实现了对共享内存的互斥访问,确保在同一时间只有一个进程可以访问共享内存,避免了数据竞争和不一致的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/36808.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

el-input 不可编辑,但是点击的时候出现弹窗/或其他操作面板,并且带可清除按钮

1.focus“getFocus”鼠标聚焦的时候写个方法&#xff0c;弹窗起来 getFocus(){ this.定义的弹窗状态字段 true;} 2.点击确定的时候&#xff0c;数值赋值到el-input的输入框,弹窗取消&#xff08;this.定义的弹段字端 false&#xff09; 3.但是会有个问题就是el-input 不可点…

Weblogic未授权远程命令执行漏洞复现

1 漏洞简介 Weblogic是Oracle公司推出的J2EE应用服务器&#xff0c;CVE-2020-14882允许未授权的用户绕过管理控制台的权限验证访问后台&#xff0c;CVE-2020-14883允许后台任意用户通过HTTP协议执行任意命令。使用这两个漏洞组成的利用链&#xff0c;可通过一个GET请求在远程W…

海康SDK协议在智联视频超融合平台中的接入方法

一. 海康SDK协议详解 海康SDK协议原理 海康SDK协议是海康威视为开发者提供的一套软件开发工具包&#xff0c;用于与海康设备&#xff08;如摄像头、NVR、DVR等&#xff09;进行通信和控制。其核心原理包括&#xff1a; 网络通信&#xff1a;基于TCP/IP协议&#xff0c;实现设…

五模型对比!Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 光伏功率预测&#xff01;五模型对比&#xff01;Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测(Matlab2023b 多输入单输出) 1.程序已经调试好&#xff0c;替换数据集后&#xff0c;仅运…

20250319在荣品的PRO-RK3566开发板的buildroot系统下使用集成的QT应用调试串口UART3

stty -F /dev/ttyS3 115200 -echo cat /dev/ttyS3 & echo serialdata > /dev/ttyS3 20250319在荣品的PRO-RK3566开发板的buildroot系统下使用集成的QT应用调试串口UART3 2025/3/19 14:17 缘起&#xff1a;在荣品的PRO-RK3566开发板的buildroot系统下&#xff0c;在命令…

Git 使用笔记

参考链接&#xff1a; 创建版本库 - Git教程 - 廖雪峰的官方网站 Git使用教程,最详细&#xff0c;最傻瓜&#xff0c;最浅显&#xff0c;真正手把手教 - 知乎 命令使用 cd f: 切换目录到 F 盘 cd gitCxl 切换目录到 gitCxl 文件夹 mkdir gitCxl 创建新文件…

Xilinx系列FPGA视频采集转HDMI2.0输出,基于HDMI 1.4/2.0 Transmitter Subsystem方案,提供6套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我已有的 GT 高速接口解决方案我已有的FPGA图像处理方案 3、详细设计方案设计框图硬件设计架构FPGA开发板输入Sensor之-->OV5640摄像头动态彩条Video In To AXI4-S…

机器学习面试重点第二部分(动画版)

​ 目录 ​ 第一章、聚类算法 ​1.1 K-means 聚类 ​1.1.1 算法​编辑流程 1.1.2 优缺点 ​1.1.3 应用场景 ​1.2 层次聚类 ​1.2.1 算法流程 1.2.2 优缺点 ​1.2.3 应用场景 ​1.3 DBSCAN ​1.3.1 算法流程 1.3.2 优缺点 ​1.3.3 应用场景 1.3.4. 参数 ε&…

剑指Offer精选:Java与Spring高频面试题深度解析

一、Java底层核心机制 &#x1f525; 问题1&#xff1a;谈谈对Java的理解&#xff1f; &#x1f4cc; 核心技术特性 平台无关性 "一次编译&#xff0c;到处运行"&#xff1a;通过JVM实现跨平台兼容 字节码&#xff08;.class&#xff09;作为中间语言&#xff0c;…

RabbitMQ 集群降配

这里写自定义目录标题 摘要检查状态1. 检查 RabbitMQ 服务状态2. 检查 RabbitMQ 端口监听3. 检查 RabbitMQ 管理插件是否启用4. 检查开机自启状态5. 确认集群高可用性6. 检查使用该集群的服务是否做了断开重连 实操1. 负载均衡配置2. 逐个节点降配&#xff08;滚动操作&#xf…

【正点原子K210连载】第七十六章 音频FFT实验 摘自【正点原子】DNK210使用指南-CanMV版指南

第七十六章 音频FFT实验 本章将介绍CanMV下FFT的应用&#xff0c;通过将时域采集到的音频数据通过FFT为频域。通过本章的学习&#xff0c;读者将学习到CanMV下控制FFT加速器进行FFT的使用。 本章分为如下几个小节&#xff1a; 32.1 maix.FFT模块介绍 32.2 硬件设计 32.3 程序设…

嵌入式开发之STM32学习笔记day08

从“门铃”到“中断”&#xff1a;手把手玩转STM32的外部中断控制器&#xff08;EXTI&#xff09; 引言&#xff1a;为什么我们需要“中断”&#xff1f; &#xff08;类比生活场景&#xff1a;用“快递按门铃”解释中断的意义&#xff09; 想象一下&#xff1a;当你在…

JVM的一些知识

JVM简介 JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java 虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。常见的虚拟机&#xff1a;JVM、VMwave、Virtual Box。 JVM 和其他两个虚拟机的区别&#xff1a; VMw…

Mac:JMeter 下载+安装+环境配置(图文详细讲解)

&#x1f4cc; 下载JMeter 下载地址&#xff1a;https://jmeter.apache.org/download_jmeter.cgi &#x1f4cc; 无需安装 Apache官网下载 JMeter 压缩包&#xff0c;无需安装&#xff0c;下载解压后放到自己指定目录下即可。 按我自己的习惯&#xff0c;我会在用户 jane 目…

【简单有效!】Gradio利用html插件实现video视频流循环播放

文章目录 前言 & 思路静态资源挂载完整代码结果示例 前言 & 思路 需要利用gradio在前端搭建一个页面&#xff0c;循环播放视频。思路是直接调用gr.HTML插件实现&#xff0c;简单有效&#xff01;&#xff01;&#xff01; 静态资源挂载 app.mount("/static&quo…

⭐算法OJ⭐克隆图【BFS】(C++实现)Clone Graph

前情提要&#xff1a;图论入门【数据结构基础】&#xff1a;什么是图&#xff1f;如何表示图&#xff1f; 133. Clone Graph Given a reference of a node in a connected undirected graph. Return a deep copy (clone) of the graph. Each node in the graph contains a va…

SpringSecurity——基于角色权限控制和资源权限控制

目录 基于角色权限控制 1.1 自定义 UserDetailsService 1.2 加载用户角色 1.3. 给角色配置能访问的资源&#xff08;使用切面拦截&#xff0c;使用注解&#xff09; 总结 资源权限控制 2.2. 需要有一个用户&#xff1b;&#xff08;从数据库查询用户&#xff09; 2.2 基…

【MySQL】表的约束

目录 零、前言一、空属性二、默认值三、列描述四、zerofill五、主键六、自增长七、唯一键八、外键结尾 零、前言 表中一定要有各种约束&#xff0c;通过约束来让用户未来插入的数据是符合要求的。约束的本质就是通过计算反过来要求用户插入正确的数据。所以站在MySQL的角度上来…

SQLMesh系列教程:SQLMesh虚拟数据环境

各种工具都已将软件工程实践引入到数据工程中&#xff0c;但仍有差距存在&#xff0c;尤其是在测试和工作流等领域。SQLMesh 的目标是在这些领域开辟新的天地&#xff0c;解决像 dbt 这样的竞争产品尚未提供强大解决方案的难题。在这篇文章中&#xff0c;我将对 SQLMesh 进行简…

基于Babylon.js的Shader入门之五:让Shader支持法线贴图

如果一个比较平坦的物体表面要添加更多的凹凸细节&#xff0c;但是我们又不想通过建模实现&#xff0c;这时候法线贴图就派上用场了。法线贴图是通过与灯光的交互来让一个平坦表面造成凹凸效果假象的&#xff0c;在基于Babylon.js的Shader入门之四&#xff1a;让Shader支持基础…