当前位置: 首页 > news >正文

Linux系统中进程间通信(Inter-Process Communication, IPC)

文章目录

  • 进程间通信介绍
    • 进程间通信目的
    • 进程间通信发展
  • 管道
    • 什么是管道
  • 匿名管道
    • 用fork来共享管道原理
    • 站在文件描述符角度-深度理解管道
    • 站在内核角度-管道本质
    • 管道读写规则
    • 管道特点
  • 命名管道
    • 创建一个命名管道
    • 匿名管道与命名管道的区别
    • 命名管道的打开规则
  • 命名管道的删除
  • 用命名管道实现文件拷贝
    • 用命名管道实现server&client通信
  • system V共享内存
    • 共享内存示意图
    • 共享内存数据结构
    • 共享内存函数
  • system V信号量 - 选学了解即可

在这里插入图片描述

进程间通信介绍

进程间通信目的

  1. 数据传输:一个进程需要将它的数据发送给另一个进程
  2. 资源共享:多个进程之间共享同样的资源。
  3. 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信发展

进程间通信(IPC)的发展经历了多个阶段,主要包括:

  • 管道(Pipes):最早期的IPC机制之一,允许具有亲缘关系的进程(如父子进程)进行通信。管道是半双工(某一时刻只允许信号在管道上的单向通信)的,数据只能单向流动。

  • 命名管道(Named Pipes):与管道类似,但它们允许没有亲缘关系的进程通信,并且可以在文件系统中有一个名称。

  • System V IPC:这是一组传统的IPC机制,包括消息队列、信号量和共享内存。这些机制允许不同进程共享和传递复杂的数据结构。

  • POSIX IPC:基于System V IPC,提供了更加标准化和可移植的IPC机制,包括消息传递、同步以及共享内存的新方法。

  • 套接字(Sockets):用于不同机器上的进程通信,也可以在同一台机器上的进程之间通信,非常灵活。

  • 远程过程调用(RPC):允许一个进程调用另一个进程的函数或方法,就像调用本地函数一样。

  • 内存映射文件(Memory-mapped files):通过映射文件到内存,不同进程可以访问同一块内存区域,实现通信。

  • 信号(Signals):用于进程间的简单通信,一个进程可以向另一个进程发送信号,以通知某些事件的发生。

随着技术的发展,IPC机制也在不断进步,以满足更高效、更安全的进程通信需求。全部的进程间通信虽然体系庞大,但是其基本思想都离不开操作系统的性质–先描述,再组织。我们只需学习早期的通信方法管道和System V通信,触类旁通。

管道

什么是管道

  1. 管道是Unix中最古老的进程间通信的形式。
  2. 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

在这里插入图片描述

匿名管道

匿名管道(Anonymous Pipe)是一种在进程间进行单向通信的机制,其中一个进程作为写入端,另一个进程作为读取端。它是一种简单而有效的进程间通信方式,常用于父子进程或兄弟进程之间的通信。

在Linux系统中,可以使用pipe函数来创建匿名管道。pipe函数会创建一个管道,并返回两个文件描述符,一个用于读取数据,另一个用于写入数据。其中,文件描述符0表示标准输入(stdin),文件描述符1表示标准输(stdout)。

#include <unistd.h>
int pipe(int fd[2]);
//创建失败返回-1,创建成功返回两个文件描述符构成的数组

以下是使用匿名管道进行进程间通信的基本步骤:

  1. 调用pipe函数创建管道,获取读取端和写入端的文件描述符。
  2. 调用fork函数创建一个子进程。
  3. 在父进程中关闭读取端的文件描述符,保留写入端的文件描述符。
  4. 在子进程中关闭写入端的文件描述符,保留读取端的文件描述符。
  5. 父进程可以通过写入端的文件描述符向管道写入数据。
  6. 子进程可以通过读取端的文件描述符从管道读取数据。

下面是一个简单的示例代码,演示了如何使用匿名管道进行进程间通信:

#include <stdio.h>
#include <unistd.h>int main() {int pipefd[2];char buffer[20];pid_t pid;// 创建管道if (pipe(pipefd) == -1) {perror("pipe");return 1;}// 创建子进程pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid == 0) {// 子进程close(pipefd[1]);  // 关闭写入端// 从管道读取数据read(pipefd[0], buffer, sizeof(buffer));printf("子进程接收到的数据:%s\n", buffer);close(pipefd[0]);  // 关闭读取端} else {// 父进程close(pipefd[0]);  // 关闭读取端// 向管道写入数据write(pipefd[1], "Hello, child process!", 21);close(pipefd[1]);  // 关闭写入端}return 0;
}

在上述示例中,父进程向管道写入了一段字符串,子进程从管道中读取该字符串并输出。通过匿名管道,父子进程之间实现了简单的通信。
需要注意的是,匿名管道只能实现单向通信,如果需要双向通信,可以考虑使用命名管道(Named Pipe)或其他进程间通信机制,如共享内存或消息队列。

用fork来共享管道原理

在这里插入图片描述

站在文件描述符角度-深度理解管道

在这里插入图片描述

站在内核角度-管道本质

在这里插入图片描述
所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

管道读写规则

当没有数据可读时

  1. O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  2. O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
    当管道满的时候
  3. O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  4. O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程
退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道特点

  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  2. 管道提供流式服务。
  3. 一般而言,进程退出,管道释放,所以管道的生命周期随进程。
  4. 一般而言,内核会对管道操作进行同步与互斥。
  5. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。如下图:
    在这里插入图片描述

命名管道

  1. 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  2. 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  3. 命名管道是一种特殊类型的文件。

创建一个命名管道

  1. 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo filename

其中,filename 是要创建的命名管道的路径和名称。
例如,要在命令行上创建一个名为 “myfifo” 的命名管道,可以执行以下命令:

mkfifo myfifo

执行该命令后,系统将在当前目录下创建一个名为 “myfifo” 的命名管道文件。

需要注意的是,命名管道是一种特殊的文件可以像普通文件一样进行读写操作。在命令行中创建的命名管道可以被其他进程打开并进行读写操作

  1. 命名管道也可以从程序里创建,相关函数有:
    mkfifo 是一个用于创建命名管道(Named Pipe)的系统调用。它可以在文件系统中创建一个特殊的文件,用于进程间的通信。

具体使用方法如下:

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
  • pathname:要创建的命名管道的路径和名称。
  • mode:创建的命名管道的权限模式。

返回值:

  • 成功:返回0。
  • 失败:返回-1,并设置相应的错误码。

使用示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>int main() {const char *pathname = "myfifo";mode_t mode = 0666; // 设置权限为读写所有者、组和其他用户if (mkfifo(pathname, mode) == -1) {perror("mkfifo");return 1;}printf("命名管道创建成功:%s\n", pathname);return 0;
}

在上述示例中,我们调用了 mkfifo 函数创建了一个命名管道,并指定了路径和名称为 “myfifo”,权限模式为 0666,表示读写所有者、组和其他用户都有权限。

需要注意的是,命名管道是一种特殊的文件,可以像普通文件一样进行读写操作。在进程间通信时,一个进程可以将数据写入命名管道,而另一个进程可以从该管道读取数据。但是,需要注意管道的读取和写入操作应该在不同的进程中进行,否则可能会造成阻塞。

匿名管道与命名管道的区别

  1. 匿名管道由pipe函数创建并打开。
  2. 命名管道由mkfifo函数创建,打开用open
  3. FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则

对于命名管道(FIFO)的打开规则,取决于打开操作是为读还是为写。

  1. 当打开操作为读时:

    • 如果打开操作没有设置 O_NONBLOCK 标志(即阻塞模式),则打开操作会阻塞,直到有其他进程以写模式打开该命名管道。
    • 如果打开操作设置了 O_NONBLOCK 标志(即非阻塞模式),则打开操作会立即返回成功,不会阻塞等待。
  2. 当打开操作为写时:

    • 如果打开操作没有设置 O_NONBLOCK 标志(即阻塞模式),则打开操作会阻塞,直到有其他进程以读模式打开该命名管道。
    • 如果打开操作设置了 O_NONBLOCK 标志(即非阻塞模式),则打开操作会立即返回失败,并返回错误码 ENXIO(表示设备或地址不存在)。

这些规则确保了在读写操作之间建立正确的通信连接。如果读操作和写操作同时进行,并且满足打开规则,那么数据可以在进程之间通过命名管道进行传输。

需要注意的是,打开规则只适用于命名管道的打开操作,而不是创建操作。创建命名管道时,并不需要等待其他进程的打开操作。只有在打开操作时,才会根据规则进行阻塞或立即返回。

命名管道的删除

用命名管道实现文件拷贝

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<stdlib.h>
#define BUFFER_SIZE 50
int main()
{const char *pathname = "myfifo";mode_t mode = 0666; // 设置权限为读写所有者、组和其他用户if (mkfifo(pathname, mode) == -1){perror("mkfifo");return 1;}printf("命名管道创建成功:%s\n", pathname);// 打开源文件和目标文件    int source_fd = open("./text.txt", O_RDONLY);if (source_fd == -1) {perror("open source file");return 1;}int destination_fd = open("./popytext.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (destination_fd == -1) {perror("open destination file");return 1;}// 打开命名管道int fifo_fd = open("myfifo", O_RDWR);if (fifo_fd == -1) {perror("open fifo");return 1;}// 创建子进程进行数据传输pid_t pid = fork();if (pid == -1) {perror("fork");return 1;}char buffer[BUFFER_SIZE];ssize_t bytes_read;if (pid == 0) {// 子进程从命名管道中读取数据并写入目标文件if((bytes_read = read(fifo_fd, buffer, BUFFER_SIZE)) > 0) {write(destination_fd, buffer, bytes_read);}exit(EXIT_SUCCESS);}// 父进程从源文件中读取数据并写入命名管道if((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) {write(fifo_fd, buffer, bytes_read);}close(source_fd);close(fifo_fd);close(destination_fd);// 等待子进程结束wait(NULL);// 删除命名管道unlink("myfifo");return 0;
}

用命名管道实现server&client通信

comm.hpp

#pragma once 
#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>#define FIFO_FILE "./myfifo"
#define MODE 0664
enum{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR, FILE_OPEN_ERR
};
class Init
{
public:Init(){int n = mkfifo(FIFO_FILE,MODE);if(n == -1){//printf("%d:%s\n",errno,strerror(errno));perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){int m = unlink(FIFO_FILE);if(m== -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};

log.hpp

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};

server.cc

#include"comm.hpp"
#include"log.hpp"using namespace std;//管理管道文件
int main()
{/*logmessage(Info,"hello linux");*///创建信道Init init;//打开管道int fd = open(FIFO_FILE,O_RDONLY);if(fd<0){//perror("open");logmessage(Fatal,"error string:%s,error code:%d",strerror(errno),errno);exit(FILE_OPEN_ERR);}logmessage(Info,"error string:%s,error code:%d",strerror(errno),errno);//开始通信while(true){char buffer[1024]={0};int x = read(fd,buffer,sizeof(buffer));if(x>0){buffer[x]=0;cout<<"client say#"<<buffer<<endl;}else if(x==0){logmessage(Debug,"client quit,me too!:%s,error code:%d",strerror(errno),errno);break;}elsebreak;}//关闭管道 close(fd);return 0;
}

client.cc

#include"comm.hpp"
using namespace std;int main()
{int fd = open(FIFO_FILE,O_WRONLY);if(fd<0){perror("open");exit(FILE_OPEN_ERR);}string line;while(true){cout<<"Please Enter@";getline(cin,line);write(fd,line.c_str(),line.size());}close(fd);return 0;
}

system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存示意图

在这里插入图片描述

共享内存数据结构

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};

这段代码定义了 struct shmid_ds 结构体,用于描述 System V 共享内存段的属性和状态。让我们逐个解释这些字段的含义:

  • shm_perm:一个 struct ipc_perm 类型的结构体,用于描述共享内存段的权限信息,包括所有者、所属组和访问权限等。

  • shm_segsz:表示共享内存段的大小,以字节为单位。

  • shm_atime:表示最后一次连接(attach)共享内存段的时间,以 UNIX 时间戳(__kernel_time_t 类型)表示。

  • shm_dtime:表示最后一次分离(detach)共享内存段的时间,以 UNIX 时间戳表示。

  • shm_ctime:表示最后一次修改共享内存段属性的时间,以 UNIX 时间戳表示。

  • shm_cpid:表示创建共享内存段的进程的进程 ID(__kernel_ipc_pid_t 类型)。

  • shm_lpid:表示最后一次操作共享内存段的进程的进程 ID。

  • shm_nattch:表示当前连接(attach)到共享内存段的进程数量。

  • shm_unused:一个未使用的字段,用于兼容性。

  • shm_unused2shm_unused3:两个未使用的字段,可能由某些特定的实现或库使用。

通过使用 struct shmid_ds 结构体,可以获取共享内存段的属性信息,例如大小、创建时间、连接数等。这些信息对于管理和监控共享内存的使用非常有用。

请注意,struct shmid_ds 结构体中的字段类型可能会因操作系统和编译器而异,例如 __kernel_time_t__kernel_ipc_pid_t。在实际使用时,你需要查阅相关的系统文档或头文件来了解具体的字段类型和定义。

共享内存函数

shmget函数

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
- key:用于标识共享内存段的键值。可以使用 `ftok` 函数生成一个唯一的键值,
- 也可以使用已知的键值来获取现有的共享内存段。不同的共享内存段应该使用不同的键值。
- size:指定共享内存段的大小,以字节为单位。共享内存段的大小应根据实际需求进行设置。
- shmflg:用于指定创建共享内存段的标志和访问权限。可以通过按位或运算符 `|` 组合多
- 个标志。常用的标志包括:
- IPC_CREAT:如果共享内存段不存在,则创建一个新的共享内存段。  
- IPC_EXCL:不单独使用,与 IPC_CREAT 一起使用,如果共享内存段已存在,则返回错误。
- 0666:指定共享内存段的访问权限。这里的权限是八进制表示,表示所有者、
- 所属组和其他用户的读写权限。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

上面的Key有讲究的:

  1. key是一个数字,这个数字是几,并不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识。
  2. 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了!
  3. 对于一个已经创建好的共享内存,key在哪? key在共享内存的描述对象中!
  4. 第一次创建的时候,必须有一个key了。怎么有?可以使用 ftok 函数生成一个唯一的键值,也可以使用已知的键值来获取现有的共享内存段。不同的共享内存段应该使用不同的键值。
    ftok 函数用于将一个路径名和一个整数标识符转换为一个唯一的键值,用于标识 System V 共享内存段、消息队列或信号量集。下面是 ftok 函数的使用方法:
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
  • pathname:一个指向路径名的字符串,可以是任意有效的文件路径。通常选择一个与共享内存相关的文件作为路径名,确保不同的共享内存段使用不同的路径名。

  • proj_id:一个整数标识符,用于区分不同的共享内存段。通常选择一个非零的整数作为标识符。

ftok 函数将 pathnameproj_id 组合起来,生成一个唯一的键值。该键值可以用于创建共享内存段、获取现有的共享内存段或操作其他 System V IPC 对象。

需要注意的是,ftok 函数的结果并不是绝对唯一的,因为它使用了路径名和整数标识符的组合。因此,在使用 ftok 生成键值时,应确保路径名和标识符的组合在整个系统中是唯一的,以避免冲突。

以下是一个示例,展示了如何使用 ftok 函数生成一个键值:

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>int main() {const char *pathname = "/path/to/file"; // 路径名int proj_id = 1; // 标识符key_t key = ftok(pathname, proj_id);if (key == -1) {perror("ftok");return 1;}printf("Generated key: %d\n", key);return 0;
}

在上述示例中,我们使用 /path/to/file 作为路径名,1 作为标识符,然后调用 ftok 函数生成一个键值。如果成功,将打印生成的键值。如果发生错误,将打印相应的错误信息。

请注意,生成的键值应与其他进程共享,以便它们可以使用相同的键值访问相同的共享内存段。

shmat函数

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明:

  1. shmaddr为NULL,核心自动选择一个地址
  2. shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
  3. shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
  4. shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
    shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

在这里插入图片描述

System V 共享内存的使用涉及以下几个步骤:

  1. 创建共享内存段:使用 shmget 函数创建一个共享内存段。该函数接受三个参数:键值(用于标识共享内存段),大小(指定共享内存的大小),和标志(用于指定权限和创建方式)。成功创建共享内存段后,shmget 函数返回一个唯一的标识符(共享内存标识符)。

  2. 连接共享内存:使用 shmat 函数将共享内存段连接到进程的地址空间。shmat 函数接受两个参数:共享内存标识符和地址(如果地址为 NULL,则系统会自动选择一个可用的地址)。成功连接共享内存后,shmat 函数返回共享内存的起始地址。

  3. 使用共享内存:一旦共享内存连接到进程的地址空间,进程可以像使用普通内存一样使用它。可以通过指针访问共享内存中的数据。

  4. 分离共享内存:使用 shmdt 函数将共享内存从进程的地址空间中分离。shmdt 函数接受一个参数,即共享内存的起始地址。成功分离共享内存后,进程将无法再访问共享内存中的数据。

  5. 删除共享内存段:使用 shmctl 函数删除共享内存段。shmctl 函数接受三个参数:共享内存标识符、命令(用于指定要执行的操作),和一个结构体指针(用于传递额外的参数)。通过指定命令为 IPC_RMID,可以删除共享内存段。

下面是一个简单的 C 语言示例,展示了如何使用 System V 共享内存:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>int main() {key_t key = ftok("shared_memory_key", 1234);  // 创建一个唯一的键值int shm_id = shmget(key, sizeof(int), IPC_CREAT | 0666);  // 创建共享内存段if (shm_id == -1) {perror("shmget");return 1;}int *shared_data = (int *)shmat(shm_id, NULL, 0);  // 连接共享内存if (shared_data == (int *)-1) {perror("shmat");return 1;}*shared_data = 42;  // 在共享内存中写入数据printf("Shared data: %d\n", *shared_data);shmdt(shared_data);  // 分离共享内存shmctl(shm_id, IPC_RMID, NULL);  // 删除共享内存段return 0;
}

在上面的示例中,我们使用 ftok 函数创建一个唯一的键值,用于标识共享内存段。然后,我们使用 shmget 函数创建一个大小为 sizeof(int) 的共享内存段,并指定了创建标志 IPC_CREAT | 0666。接下来,我们使用 shmat 函数将共享内存连接到进程的地址空间,并将其强制转换为 int 类型的指针。然后,我们可以通过指针访问共享内存中的数据。最后,我们使用 shmdt 函数分离共享内存,并使用 shmctl 函数删除共享内存段。

请注意,System V 共享内存是一种底层的机制,需要谨慎使用,以确保正确的同步和互斥。在实际应用中,你可能需要使用其他同步机制(如信号量或互斥锁)来保护共享内存的访问。

system V信号量 - 选学了解即可

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。
在多进程或多线程的环境中,进程互斥(Process Mutual Exclusion)是指一种机制,用于确保同时只有一个进程(或线程)可以访问共享资源,以避免数据竞争和不一致的结果。

当多个进程或线程同时访问共享资源时,可能会出现以下问题:

  1. 竞态条件(Race Condition):多个进程或线程以不可预测的方式相互干扰,导致结果的正确性无法保证。

  2. 数据不一致:多个进程或线程对共享资源进行读写操作时,可能会导致数据的不一致性,破坏程序的正确性。

为了解决这些问题,需要使用进程互斥机制来确保共享资源的互斥访问。常见的进程互斥机制包括使用互斥锁(Mutex)、信号量(Semaphore)和临界区(Critical Section)等。

互斥锁是一种最常见的进程互斥机制,它提供了两个基本操作:上锁(Lock)和解锁(Unlock)。当一个进程或线程需要访问共享资源时,它会尝试上锁,如果成功获取到锁,则可以访问共享资源;如果锁已经被其他进程或线程持有,则该进程或线程会被阻塞,直到锁被释放。

通过使用互斥锁,可以确保同一时间只有一个进程或线程可以访问共享资源,从而避免了竞态条件和数据不一致的问题。

需要注意的是,进程互斥只是一种机制,它并不能解决所有的并发问题。在设计并发程序时,还需要考虑其他因素,如死锁(Deadlock)和活锁(Livelock)等。

进程同步(Process Synchronization)是指在多个进程或线程之间协调和控制它们的执行顺序,以确保它们按照一定的顺序和时间间隔执行。

在并发环境中,多个进程或线程可能会同时执行,并且彼此之间的执行速度和顺序是不确定的。这可能导致一些问题,如竞态条件、数据不一致和死锁等。

进程同步的目的是通过使用同步机制来协调和控制进程或线程的执行,以避免这些问题。常见的进程同步机制包括互斥锁、信号量、条件变量和屏障等。

以下是一些常见的进程同步场景:

  1. 互斥访问共享资源:多个进程或线程需要访问共享资源时,通过使用互斥锁来确保同一时间只有一个进程或线程可以访问共享资源,避免数据竞争和不一致的结果。

  2. 同步执行顺序:在某些情况下,需要确保多个进程或线程按照特定的顺序执行。例如,一个进程可能需要等待另一个进程完成某个任务后才能继续执行。这可以通过使用信号量或条件变量来实现。

  3. 生产者-消费者问题:在生产者-消费者问题中,多个生产者进程和消费者进程共享一个有限的缓冲区。生产者进程将数据放入缓冲区,而消费者进程从缓冲区中取出数据。需要确保生产者和消费者之间的操作同步,以避免缓冲区溢出或下溢。这可以通过使用信号量来实现。

进程同步是并发编程中的重要概念,它可以帮助解决并发执行时可能出现的问题,确保程序的正确性和一致性。

总之以上是进程间通信的基本概念,在以后的学习中会慢慢深入进程更加详细的知识。

相关文章:

Linux系统中进程间通信(Inter-Process Communication, IPC)

文章目录 进程间通信介绍进程间通信目的进程间通信发展 管道什么是管道 匿名管道用fork来共享管道原理站在文件描述符角度-深度理解管道站在内核角度-管道本质管道读写规则管道特点 命名管道创建一个命名管道匿名管道与命名管道的区别命名管道的打开规则 命名管道的删除用命名管…...

【React + Typescript】使用WebPack包管理、各种扩展插件组成的初始模板,开源协议:CC-BY-4.0

React Typescript Webpack 模板 模板展示项目结构使用的部分扩展包页面配置代码Layout 公共容器组件路由Jspackage.json 开源模板下载TIP 模板展示 项目结构 使用的部分扩展包 &#x1f4c2; System ├── &#x1f4c2; Plugin │ ├── &#x1f4c4; file-loader | 在处…...

python 制作3d立体隐藏图

生成文件的3d图&#xff0c;例子&#xff1a; 文字&#xff1a; 隐藏图&#xff1a; 使用建议&#xff1a; &#xff11;、建议不用中文&#xff0c;因为中文太复杂&#xff0c;生成立体图效果不好。 &#xff12;、需要指定FONT_PATH&#xff0c;为一个ttf文件&#xff0c;…...

layui+ssm实现数据批量删除

layuissm实现数据的批量删除 //数据表格table.render({id: adminList,elem: #adminList,url: ctx "/admin/getAdminList", //数据接口cellMinWidth: 80,even: true,toolbar: #toolbarDemo,//头部工具栏limit: 10,//每页条数limits: [10, 20, 30, 40],defaultToolba…...

国产AI边缘计算盒子,双核心A55丨2.5Tops算力

边缘计算盒子 双核心A55丨2.5Tops算力 ● 2.5TopsINT8算力&#xff0c;支持INT8/INT4/FP16多精度混合量化。 ● 4路以上1080p30fps视频编解码&#xff0c;IVE模块独立提供图像基础算子加速。 ● 支持Caffe、ONNX/PyTorch深度学习框架&#xff0c;提供resnet50、yolov5等AI算…...

C++作业4

代码整理&#xff0c; 将学过的三种运算符重载&#xff0c;每个至少实现一个运算符的重载 代码&#xff1a; #include <iostream>using namespace std;class Stu {friend const Stu operator*(const Stu &L,const Stu &R);friend bool operator<(const Stu …...

计算机网络(二)| 物理层上 | 数据通信基础知识 调制 频率范围 信噪比

文章目录 1 物理层基本概念2.数据通信基础知识2.1 数据通信基本概念2.2 信道基本概念2.2.1 基带调制&#xff08;编码&#xff09;方式2.2.2 带通调制方式 2.3 信道的极限速率影响因素2.3.1 **频率范围**2.3.2 **信噪比** 内容笔记来源于谢希任老师《计算机网络》 物理层重点 …...

[STM32-1.点灯大师上线】

学习了江协科技的前4课&#xff0c;除了打开套件的第一秒是开心的&#xff0c;后面的时间都是在骂娘。因为51的基础已经几乎忘干净&#xff0c;c语言已经还给谭浩强&#xff0c;模电数电还有点底子&#xff0c;硬着头皮上吧。 本篇主要是讲述学习点灯的过程和疑惑解释。 1.工…...

Web测试自动化工具Selenium的使用

Web测试自动化工具Selenium的使用 Selenium是一个Web应用测试的自动化工具&#xff0c;它通过模拟点击实现对Web应用的功能测试。测试时&#xff0c;除了Selenium&#xff0c;还需要对应的浏览器驱动&#xff0c;如在Chrome实现自动点击&#xff0c;则需要chromedriver。 Sel…...

VUE2+THREE.JS 按照行动轨迹移动人物模型并相机视角跟随人物

按照行动轨迹移动人物模型并相机视角跟随人物 1. 初始化加载模型2. 开始移动模型3. 人物模型启动4. 暂停模型移动5. 重置模型位置6. 切换区域动画7. 摄像机追踪模型8. 移动模型位置9.动画执行 人物按照上一篇博客所设定的关键点位置&#xff0c;匀速移动 1. 初始化加载模型 //…...

Hadoop YARN组件

1. 请解释Yarn的基本架构和工作原理。 YARN&#xff0c;也被称为"Yet Another Resource Negotiator"&#xff0c;是Apache HadoopYARN&#xff0c;也被称为"Yet Another Resource Negotiator"&#xff0c;是Apache Hadoop的一部分&#xff0c;它被设计为一…...

Java架构师技术架构路线

目录 1 概论2 如何规划短中长期的技术架构路线图3 如何规划面向未来的架构4 如何修订路线图执行过程中的偏差5 如何落地路线图-阿里系糙快猛之下的敏捷模式想学习架构师构建流程请跳转:Java架构师系统架构设计 1 概论 首先,规划一个短中长期的技术路线图是非常重要的。短中…...

guacamole docker一键部署脚本

前言 在我学习guacamole的过程中发现全网大致有两种方式安装guacamole的方式&#xff1a; 1. 直接安装&#xff08;下载java环境/mysql/, 修改配置&#xff09; 2. docker安装&#xff08;和直接安装类似&#xff0c;需要下载相关环境&#xff0c;然后做配置&#xff09; 然…...

蓝桥杯算法心得——想吃冰淇淋和蛋糕(dp)

大家好&#xff0c;我是晴天学长&#xff0c;dp题&#xff0c;怎么设计状态很重要&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .想吃冰淇淋和蛋糕 想吃冰淇淋与蛋糕 输入格式 第一行输入一个整数n。…...

LLM之RAG实战(二):使用LlamaIndex + Metaphor实现知识工作自动化

最先进的大型语言模型&#xff08;LLM&#xff09;&#xff0c;如ChatGPT、GPT-4、Claude 2&#xff0c;具有令人难以置信的推理能力&#xff0c;可以解锁各种用例——从洞察力提取到问答&#xff0c;再到通用工作流自动化。然而&#xff0c;他们检索上下文相关信息的能力有限。…...

【容器】Docker打包Linux操作系统迁移

0x0 场景 因老服务器操作系统文centos6.5&#xff0c;现要迁移至uos v20 1050a&#xff08;底层centos8&#xff09;&#xff0c;其中需要迁移的应用组件有&#xff1a; mysql 、tomcat、apachehttpd&#xff0c;因版本跨越太大&#xff0c;导致centos8直接安装无法完全恢复原…...

redis基本数据结构

Redis入门&#xff1a;五大数据类型 文章目录 Redis入门&#xff1a;五大数据类型一.概述二.Redis的基本了解三.Redis五大数据类型1.String (字符串)2.List(列表)3.Set集合(元素唯一不重复)4.Hash集合5.zSet(有序集合) 一.概述 什么是Redis Redis&#xff08;Remote Dictiona…...

Learning Normal Dynamics in Videos with Meta Prototype Network 论文阅读

文章信息&#xff1a;发表在cvpr2021 原文链接&#xff1a; Learning Normal Dynamics in Videos with Meta Prototype Network 摘要1.介绍2.相关工作3.方法3.1. Dynamic Prototype Unit3.2. 视频异常检测的目标函数3.3. 少样本视频异常检测中的元学习 4.实验5.总结代码复现&a…...

Unity 关于SpriteRenderer 和正交相机缩放

float oldWidth 750f;float oldHeight 1334f;float newWidth Screen.width;float newHeight Screen.height;float oldAspect oldWidth / oldHeight;float newAspect newWidth / newHeight;//水平方向缩放float horizontalCompressionRatio newAspect / oldAspect;//垂直…...

HarmonyOS应用开发者基础认证考试(98分答案)

基于最近大家都在考这个应用开发者基础认证考试&#xff0c;因此出了一期&#xff0c;一样复制word里面搜索做&#xff0c;很快&#xff0c;当然good luck 判断题 Ability是系统调度应用的最小单元,是能够完成一个独立功能的组件。一个应用可以包含一个或多个Ability。 正确(Tr…...

Ubuntu20.04 Kimera Semantic运行记录

Ubuntu20.04 Kimera Semantic 官方bag运行记录 以下基本为官方教程&#xff0c;有部分修改 依赖 sudo apt-get install python3-wstool python3-catkin-tools protobuf-compiler autoconf sudo apt-get install ros-noetic-cmake-modulessudo apt-get install ros-noetic-i…...

服务器RAID系统的常见故障,结合应用场景谈谈常规的维修处理流程

常见的服务器RAID系统故障包括硬盘故障、控制器故障、电源故障、写入错误和热插拔错误。下面结合这些故障的应用场景和常规维修处理流程来详细讨论&#xff1a; 硬盘故障&#xff1a; 应用场景&#xff1a;在服务器RAID系统中&#xff0c;硬盘故障是最常见的问题之一。硬盘可能…...

计算机网络——数据链路层-封装成帧(帧定界、透明传输-字节填充,比特填充、MTU)

目录 介绍 帧定界 PPP帧 以太网帧 透明传输 字节填充&#xff08;字符填充&#xff09; 比特填充 比特填充习题 MTU 介绍 所谓封装成帧&#xff0c;就是指数据链路层给上层交付下来的协议数据单元添加帧头和帧尾&#xff0c;使之成为帧。 例如下图所示&#xff1a; …...

MySQL笔记-第03章_基本的SELECT语句

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第03章_基本的SELECT语句1. SQL概述1.1 SQL背景知识1.2 SQL语言排行榜1.3 SQL 分类 2. SQL语言的规则与规范2.1 基本规则2.2 SQL大小写规范 …...

FTP服务文件上传失败,错误码553的排故过程

本文主要记录文件上传失败&#xff0c;错误码553的排故过程。 1 背景 树莓派通过FTP给嵌入式板卡传输文件&#xff0c;好几套设备&#xff0c;发现有的能传输成功&#xff0c;有的传输不成功。树莓派和嵌入式板卡都一样的&#xff0c;出现问题时感觉很懵。 2 逐项对比 2.1 自…...

音频录制软件哪个好?帮助你找到最合适的一款

音频录制软件是日常工作、学习和创作中不可或缺的一部分。选择一个适合自己需求的录音软件对于确保音频质量和提高工作效率至关重要。可是您知道音频录制软件哪个好吗&#xff1f;本文将深入探讨两种常见的音频录制软件&#xff0c;通过详细的步骤指南&#xff0c;帮助您了解它…...

9.Unity搭建HTTP服务器

搭建HTTP服务器的几种方式 //1.使用别人做好的HTTP服务器软件&#xff0c;一般作为资源服务器时使用该方式&#xff08;学习阶段建议使用&#xff09; //2.自己编写HTTP服务器应用程序&#xff0c;一般作为Web服务器 或者 短链接游戏服务器时 使用该方式 使用别人做好的HTTP服…...

C# 热键注册工具类

写在前面 介绍一个验证过的热键注册工具类&#xff0c;使用系统类库user32.dll中的RegisterHotkey函数来实现全局热键的注册。 代码实现 [Flags]public enum KeyModifiers{Alt 1,Control 2,Shift 4,Windows 8,NoRepeat 0x4000}public static class HotKeyHelper{[DllImp…...

nodejs微信小程序+python+PHP天天网站书城管理系统的设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…...

Hive环境准备[重点学习]

1.前提启动hadoop集群 hadoop在统一虚拟机中已经配置了环境变量 启动hdfs和yarn集群 命令:start-all.sh [rootnode1 /]# start-all.sh启动mr历史服务 命令:mapred --daemon start historyserver [rootnode1 /]# mapred --daemon start historyserver检查服务 命令:jps [r…...

网页视频下载软件免费版/资源网站排名优化seo

给定两个整数 nnn 和 kkk&#xff0c;请你找到并输出能够整除 nnn 的第 kkk 小的正整数。 输入格式 一行&#xff0c;两个整数 nnn 和 kkk。 输出格式 输出能够整除 nnn 的第 kkk 小的整数。 如果不存在&#xff0c;则输出 −1−1−1。 数据范围 1≤n≤10151≤n≤10^{15}1≤…...

网站流量10g/app开发平台开发

一、拼多多做直播的目的&#xff1f;1、拼多多直播毋庸置疑是为了服务商家&#xff0c;帮助商家快速变现&#xff1b;2、丰富商家的营销玩法&#xff0c;目前5g时代&#xff0c;直播势在必行&#xff1b;3、为了加大促进私域流量的一个转换&#xff1b;4、无论是中小型还是头部…...

保定seo建站/长沙seo外包

1、pushbutton->default属性为true&#xff0c;按回车相当于点击该按钮。 2、选中checkable后&#xff0c;Button变成切换按钮(toggle button)&#xff0c;可以有两种状态&#xff1a;按下/弹起&#xff0c;默认状况下checkable是不选中的&#xff0c;Button默认为触发按钮(…...

做网站编辑好吗/百度应用商店app下载安装

如果你感觉累&#xff0c;那就对了那是因为你在走上坡路。。这句话似乎有点道理的样子&#xff0c;时常提醒自己无论走到哪都不要忘记自己当初为什么出发。有时想想感觉有的东西可以记录一下&#xff0c;就把它记录下来吧&#xff0c;这次想写一下关于单张图片点击全屏预览的问…...

wordpress属于区域连技术吗/上海网站seo外包

漏洞描述 GoCD plugin aip 参数中的 pluginName 参数存在任意文件读取漏洞&#xff0c;导致攻击者可以获取服务器中的任意敏感信息 fofa语法 title“Create a pipeline - Go” POC /go/add-on/business-continuity/api/plugin?folderName&pluginName../../../etc/pas…...

网站怎么申请支付宝/360营销平台

今天考完软考的网络工程师了&#xff0c;考的挺好的。心里没多大的感想&#xff0c;毕竟一直在学习&#xff0c;刚好最近一直在做ipsec 的实验&#xff0c;然后最后一题就考了。看到试题&#xff0c;心里的感想挺多的。人与人之间是不能架起的。事情走到今天&#xff0c;也已经…...