Linux系统编程:进程间通信总结

管道

在Linux中,管道是一种进程间通信方式,它允许一个进程(写入端)将其输出直接连接到另一个进程(读取端)的输入。从本质上说,管道也是一种文件,但它又和一般的文件有所不同。

具体来说,管道有两种类型:无名管道和有名管道。无名管道是指以匿名管道的方式连接两个进程,只能在具有亲缘关系的进程之间使用,例如父子进程。有名管道则是一种文件类型,允许无关进程之间的通信,即可以在非亲缘关系的进程之间使用。

无名管道通过文件描述符创建,其中文件描述符0(stdin)用于读取管道的读取端,文件描述符1(stdout)用于写入管道的写入端。有名管道则通过文件系统中的某一个文件名来建立,其它进程(不论是父子进程还是非亲缘关系进程)可以通过打开该文件来进行通信。

使用管道时,需要注意以下几点:

管道是单向的,只能从写入端向读取端传递数据。
管道的大小是有限的,当管道被填满时,写入端将无法继续写入数据。
读取端不能比写入端更快地读取数据,否则会导致读取端阻塞。
有名管道可以跨越不同进程号和主机之间的通信,但需要先用 mkfifo 命令创建管道文件。
使用完毕后,需要手动删除管道文件。

让我们更深入地理解一下无名管道的工作方式。在Linux中,每个进程都有一个标准输入(stdin),一个标准输出(stdout)和一个标准错误(stderr)。无名管道就是利用了进程的stdin和stdout来实现的。

具体来说,当一个进程需要将输出发送到另一个进程的输入时,可以使用管道。管道的写入端与发送进程的stdout相连接,而管道的读取端与接收进程的stdin相连接。这样,发送进程的输出就可以直接被接收进程读取。

例如,在shell中可以通过“|”符号来实现无名管道的功能。例如:“ls -al | grep .txt”命令中,管道连接了“ls -al”和“grep .txt”两个命令,将“ls -al”命令的输出作为“grep .txt”命令的输入,从而实现文件名搜索的功能。

接下来是有名管道。有名管道是一种在文件系统中存在的特殊文件,它允许无关的进程之间进行通信。与无名管道只能在具有亲缘关系的进程之间使用不同,有名管道可以在任何进程之间使用,不论它们是否有亲缘关系。

有名管道通过文件系统中的文件名来建立,然后其他进程可以通过打开这个文件来进行通信。使用有名管道时,需要先用“mkfifo”命令创建管道文件,然后通过文件I/O操作来进行数据的读写。

这就是Linux中的管道概念的基本解释。它是一种非常强大的工具,可以用于在不同的进程之间传递数据,使得这些进程可以协同工作。

匿名管道

匿名管道通过pipe()函数实现:
在Linux中,pipe()函数是用于创建无名管道的函数。它允许将一个进程的输出直接连接到另一个进程的输入,从而实现在不同进程之间进行数据传递。

pipe()函数在头文件unistd.h中声明,其原型如下:

int pipe(int pipefd[2]);

pipefd是一个包含两个整数的数组,用于存储管道的文件描述符。pipefd[0]是管道的读取端,pipefd[1]`是管道的写入端。

当pipe()函数成功时,返回值为0;如果出现错误,返回值为-1,并设置errno来表示错误原因。

注意,在父进程和子进程中要分别关闭管道的读取端和写入端,以避免资源泄漏。

实例

这个实例的功能:建立一个父子进程关系,让父进程往管道当中写,子进程从管道当中读出来数据:

在这里插入图片描述
在这里插入图片描述
运行结果:
在这里插入图片描述

命名管道

命名管道通过mkfifo()函数实现:
mkfifo是一个在Linux系统中用于创建命名管道的函数,也被称为FIFO文件。命名管道是一种特殊类型的文件,它被设计为在进程间通信(IPC)中临时存储数据。FIFO文件通常在shell中使用,例如mkfifo /tmp/myfifo。

mkfifo函数的原型如下:

#include <sys/types.h>  
#include <sys/stat.h>  int mkfifo(const char *pathname, mode_t mode);

mkfifo`函数需要两个参数:

pathname:这是要创建的FIFO文件的路径名。
mode:这是文件的权限位。这通常包括读、写和执行权限,就像在chmod函数中使用的一样。

这个函数会创建一个具有指定权限的FIFO文件,如果文件已经存在,那么它将被打开并返回一个文件描述符。如果文件不存在,那么一个新的文件将被创建。

当mkfifo成功时,返回值为0。如果失败,返回值为-1,并设置全局变量errno以指示错误原因。可能的错误包括权限问题、磁盘空间不足、文件系统错误等。

实例

以下是一个简单的使用mkfifo的例子:

#include <sys/types.h>  
#include <sys/stat.h>  
#include <stdio.h>  int main() {  int result = mkfifo("/tmp/myfifo", 0666);  if (result == -1) {  perror("mkfifo");  return 1;  }  return 0;  
}

在这个例子中,我们尝试在"/tmp"目录下创建一个名为"myfifo"的FIFO文件,文件的权限设置为0666(也就是用户、组和其他所有人都可以读和写)。如果创建失败,我们打印出错误信息并返回1。

XSI(也就是SysV系列进程通信手段)

XSI是X/Open公司制定用于进程通信的系统接口,是UNIX系统V版本中定义的一组系统调用和信号的集合。在Linux系统中,XSI提供了一种用于进程间通信(IPC)的机制。

XSI进程间通信需要借助系统内核,需要创建内核对象,内核对象会以整数形式返回给用户态,相当于文件描述符,也叫IPC标识符。

XSI包括信号量、共享内存、消息队列和信号等进程间通信方式。其中,信号量是一个计数器,可以对计数器进行加操作和减操作。在加操作时,计数器立即返回;在减操作时,如果计数器的当前值足够进行减操作,则减操作不会被阻塞,如果计数器的当前值不够进行减操作,则阻塞等待直到计数器的值足够进行减操作。

此外,XSI还包括一些创建和管理共享内存的函数,例如shmget用于创建或获取共享内存,shmat用于将共享内存映射到进程的虚拟地址空间等。

在Linux中使用 ipcs 命令可以查看当前系统中存在的各种IPC(Inner-Process Communication进程间通信)机制情况:
在这里插入图片描述
这三种方式都可以用于有亲缘关系的进程间通信,也可以用于没有亲缘关系的进程间通信。

从上图中可以看到,,每一种通信机制都有一个key,这个key的作用就是用来确保没有亲缘关系的两个进程之间通信时拿到的是同一个通信机制(有亲缘关系的就很简单了,不需要额外的操作,因为是fork出来的)。

通过ftok函数我们可以拿到通信机制的key。

ftok()

ftok是Linux系统下的一个函数,主要用于创建唯一的键值(称为令牌)以用于标识文件系统对象,比如共享内存、消息队列和信号量等System V IPC对象。

函数原型如下:

#include <sys/ipc.h>  key_t ftok(const char *pathname, int proj_id);

ftok`函数的参数包括:

pathname:一个已经存在的文件或目录的路径。该函数将通过这个路径来生成一个唯一的键值。
proj_id:这是一个用户定义的整数,用于在多个进程之间区分不同的键值。通常,如果两个进程使用相同的pathname和proj_id调用ftok,那么它们将得到相同的键值。

函数的返回值是一个key_t类型的键值,该键值可以被用作System V IPC对象的标识符。如果函数执行失败,将返回-1。

值得注意的是,虽然ftok函数生成的键值在大多数情况下都是唯一的,但在某些情况下,两个不同的路径可能会生成相同的键值。此外,不同的文件系统或操作系统可能会以不同的方式实现ftok函数,因此在不同的系统之间不能保证键值的唯一性。这就是为什么在使用ftok函数时,最好选择一个唯一的proj_id,并在可能的情况下避免在不同的系统之间共享键值。

IPC机制系列函数

对于每一种IPC机制,基本都有 XXXget()、XXXop()、XXXctl()。

其中XXX是每一种IPC机制的缩写,消息队列的缩写是msg,信号量数组的缩写是sem,共享内存是shm:

在这里插入图片描述
接下来将分别介绍这些函数。

消息队列

Linux下的消息队列机制是System V IPC(进程间通信)的一种实现方式,允许进程之间以消息的形式传递数据。与管道、信号和共享内存等其他IPC机制相比,消息队列具有一些独特的优点,例如可以跨多个进程传递数据、可以实现异步通信、可以避免忙轮询等。

消息队列的实现原理可以简单地描述为:在内存中创建一个队列数据结构,进程可以向该队列中写入消息,也可以从该队列中读取消息。每个消息都有一个类型和长度,类型用于区分不同的消息,长度则用于限制消息的大小。进程可以通过指定消息类型来读取或写入特定类型的消息,也可以通过指定消息的优先级来控制消息的读写顺序。

在Linux系统中,消息队列是通过消息队列标识符(也称为消息队列ID)来访问的。进程可以使用msgget函数来获取或创建一个消息队列,该函数会根据指定的键值生成一个唯一的消息队列标识符。一旦获取到消息队列标识符,进程就可以使用msgsnd函数向队列中发送消息,或者使用msgrcv函数从队列中接收消息。同时,进程还可以使用msgctl函数来控制消息队列的属性,例如设置权限、删除队列等。

需要注意的是,Linux系统对消息队列的数量和大小都有限制,因此在使用消息队列时需要注意这些限制,并根据实际需求进行合理的设计和使用。此外,由于消息队列是保存在内存中的,因此在系统重启后,所有的消息队列都会被清空。如果需要长期保存数据,可以考虑使用文件或其他持久化存储方式来实现进程间通信。

msgget()

msgget是Linux系统下的一个函数,用于获取或创建一个消息队列。它是System V消息队列子系统的一部分,通常用于进程间的通信(IPC)。

函数原型如下:

#include <sys/ipc.h>  int msgget(key_t key, int flags);

msgget`函数的参数包括:

key:一个由ftok函数生成的键值,用于标识消息队列。如果这个键值是0,那么msgget将创建一个新的消息队列。
flags:这是一个标志位,用于控制msgget的行为。最常用的标志是IPC_CRE,它告诉msgget如果消息队列不存在则创建一个新的消息队列。

函数的返回值是一个指向消息队列的描述符(即消息队列的标识符),如果函数执行失败,将返回-1。

在使用msgget函数时,需要注意以下几点:

消息队列是进程间通信的一种方式,因此需要谨慎地使用,避免在多个进程之间造成混乱。
消息队列的创建和使用都需要使用适当的权限(即flags参数需要按位或上一个权限值)。一般来说,只有具有适当权限的用户或进程才能创建和使用消息队列。
消息队列是有限制的,因此在使用时需要了解其限制,例如消息的最大长度和队列的最大数量等。
消息队列在使用完毕后需要使用msgctl函数来释放和删除。

msgop()

msgop并不是Linux下的一个具体函数,指的是涉及消息队列的一系列操作。是和消息队列相关的函数,比如msgsnd、msgrcv和msgctl等。

msgsnd: 此函数用于将消息添加到消息队列中。它接受四个参数:消息队列的标识符、指向要发送的消息的指针、消息的大小以及一个标志位。这个函数在成功时返回0,在失败时返回-1。
msgrcv: 此函数用于从消息队列中接收消息。它接受五个参数:消息队列的标识符、指向接收消息的缓冲区的指针、缓冲区的大小、要接收的消息的类型以及一个标志位。这个函数在成功时返回接收到的消息的大小,在失败或没有消息可接收时返回-1。
msgctl: 此函数用于控制消息队列,包括设置权限、获取状态以及删除消息队列等操作。它接受三个参数:消息队列的标识符、要执行的操作以及一个可选的参数,该参数根据要执行的操作而有所不同。这个函数在成功时返回0,在失败时返回-1。

在使用这些函数时,你需要注意一些事项:

首先,你需要使用msgget函数来获取或创建一个消息队列。这个函数会返回一个唯一的消息队列标识符,你可以使用这个标识符来进行后续的消息发送和接收操作。
其次,你需要注意消息的大小和类型。每个消息都有一个大小和一个类型,大小用于限制消息的长度,类型用于区分不同的消息。你可以在发送和接收消息时指定消息的大小和类型。
最后,你需要注意权限和错误处理。只有具有适当权限的进程才能创建和使用消息队列。同时,你需要检查函数的返回值以处理可能的错误。

实例

在这里插入图片描述
建立三个文件,proto就是协议,用来约定双方的对话格式,sender为发送方发送一个学生信息,rcver为接收方接收该学生信息:
proto.h:

#ifndef PROTO_H__#define PROTO_H__//对话双方需要拿到同一个msg id,那么就必须要得到同一个key值
//所以我们在协议中约定好ftok的两个参数,以确保它们拿到的是同一个key
//KEYPATH就是ftok函数的第一个参数
#define KEYPATH "/etc/services"
//KEYPROJ 是第二个参数,用字母是因为这样就能知道其为一个整形,有单位比较规范
#define KEYPROJ 'g'
#define NAMESIZE 32//需要发送的学生信息格式的封装
struct msg_st{long mtype;//这个是由msgrcv函数的第二个参数消息格式必须要的,表示消息类型,本例用不到但必须存在char name[NAMESIZE];int math;int chinese;
};#endif

rcver.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>#include "proto.h"int main(){key_t key;struct msg_st rbuf;int msgid;//先拿到keykey = ftok(KEYPATH,KEYPROJ);if(key < 0){perror("ftok()");exit(1);}//根据拿到的key创建消息队列实例,注意要按位或上权限信息嗷msgid = msgget(key,IPC_CREAT|0600);if(msgid < 0){perror("msgget()");exit(1);}//从消息队列中拿到数据//第一个参数是消息队列实例的id值//第二个参数是接收到的消息数据所要存放的地方//第三个参数表示接收来的数据类型大小(自己需要的数据的大小,所以-long,其实不减也行)//第四个参数指定要接收的消息的类型,为0就是不挑//第五个参数指定一些特殊要求,为0就是没有while(1){//循环接收打印数据if(msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0) < 0){perror("msgrcv()");exit(1);}printf("NAME = %s\n",rbuf.name);printf("MATH = %d\n",rbuf.math);printf("CHINESE = %d\n",rbuf.chinese);}//销毁消息队列实例//第一个参数是要进行控制的消息队列实例//第二个参数是要进行的操作命令//第三个参数是第二个参数操作的传参,这里没有,写NULLmsgctl(msgid,IPC_RMID,NULL);exit(0);
}

sender.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#include "proto.h"int main(){key_t key;struct msg_st sbuf;//先拿到keykey = ftok(KEYPATH,KEYPROJ);if(key < 0){perror("ftok()");exit(1);}//根据拿到的key获得消息队列实例即可//因为通信的双方只要有一方创建了消息队列即可//本程序中已经由接收方创建了,所以这里写0表示没有特殊操作int msgid = msgget(key,0);if(msgid < 0){perror("msgget()");exit(1);}//发送数据//先构造数据sbuf.mtype = 1; //这个值大于0即可//这里注意C语言的语法要求//name是一个数组名,是一块连续空间的起始位置//如果没有在一开始定义的时候就进行初始化赋值//那么后面再进行赋值时其将成为常量,常量不能在赋值号的左边出现嗷//所以只能使用strcpy进行赋值strcpy(sbuf.name,"Alan");sbuf.math = rand()%100;sbuf.chinese = rand()%100;if(msgsnd(msgid,&sbuf,sizeof(sbuf)-sizeof(long),0) < 0){perror("msgsnd()");exit(1);}puts("ok!");exit(0);
}

先运行接收方:
在这里插入图片描述
再运行发送方:
在这里插入图片描述
再查看接收方:
在这里插入图片描述
可以看见已经接收到了发送过来的数据。
我们也可以不先运行接收方,直接让发送方连发三次数据,当接收方运行的时候就会一下子接收到三条信息,因为消息队列中缓存的有数据,只要去相同的消息队列实例中拿取数据即可。

信号量数组

信号量数组是一个由多个信号量组成的数据结构,每个信号量都可以用来表示一个资源的使用情况。在Linux系统中,信号量数组被广泛用于进程间通信和同步,以及保护共享资源。

每个信号量都是一个计数器,可以用来表示资源的可用数量。当一个进程需要使用共享资源时,它需要先读取信号量的值。如果信号量的值大于0,表示资源可用,进程可以使用该资源。如果信号量的值为0,表示资源不可用,进程需要等待,直到资源可用。

在Linux中,信号量数组通常用于管理多个共享资源的情况。例如,当多个进程需要访问同一个文件时,可以使用信号量数组来保护文件的访问权限。当一个进程正在访问文件时,其他进程需要等待该进程释放资源后才能访问文件。这样可以避免多个进程同时访问同一个文件,从而防止文件损坏或数据不一致的问题。

需要注意的是,在Linux中,信号量数组的操作是原子操作,即它们是线程安全的。这意味着在多个进程同时访问信号量数组时,不会出现竞争条件或死锁的情况。

semget()

semget函数是Linux系统调用的一部分,用于获取或创建一个信号量。信号量是一种同步机制,用于解决多进程之间的同步和协调问题。

函数原型如下:

#include <sys/ipc.h>  int semget(key_t key, int nsems, int semflg);

semget`函数的参数包括:

key:一个由ftok函数生成的键值,用于标识信号量集。如果这个键值是0,那么semget将创建一个新的信号量集。
nsems:指定信号量集中的信号量数量。通常,这个值被设置为1,表示只创建一个单一的信号量。
semflg:标志位,用于控制semget的行为。最常用的标志是IPC_CRE,它告诉semget如果信号量集不存在则创建一个新的信号量集。

函数的返回值是一个指向信号量集的标识符(即信号量集的ID),如果函数执行失败,将返回-1。

在使用semget函数时,需要注意以下几点:

信号量是进程间通信的一种方式,因此需要谨慎地使用,避免在多个进程之间造成混乱。
信号量集的创建和使用都需要使用适当的权限。一般来说,只有具有适当权限的用户或进程才能创建和使用信号量集。
信号量是有限制的,因此在使用时需要了解其限制,例如最大数量和最大值等。
信号量集在使用完毕后需要使用semctl函数来释放和删除。

总之,semget函数用于获取或创建信号量集,它是Linux下进程间通信的一种机制,可以用于解决多进程之间的同步和协调问题。

semop()

semop函数是Linux系统下用于操作信号量的函数之一。信号量是一种用于进程间同步的机制,通过它可以协调多个进程之间的访问和修改共享资源。

semop函数的原型如下:

#include <sys/ipc.h>  
#include <sys/sem.h>  int semop(int semid, struct sembuf *sops, size_t nsops);

参数说明:

semid:信号量集的唯一标识符,通常通过semget函数获取。

sops:指向一个sembuf结构体的指针,该结构体定义了要执行的操作。sembuf结构体包含三个成员:

sem_num:指定要操作的信号量在信号量集中的编号(从0开始)。

sem_op:指定要执行的操作。正值表示要增加信号量的值,负值表示要减少信号量的值。如果信号量的当前值小于要减少的值,则操作将阻塞,直到信号量的值足够大。

sem_flg:指定操作的标志位。常用的标志包括IPC_NOWAIT(非阻塞操作)和SEM_UNDO(在操作完成后自动撤销对信号量的改变)。

nsops:指定要执行的操作的数量,即sops数组中结构体的数量。

返回值:如果函数执行成功,返回0;如果执行失败,返回-1并设置相应的错误码。

使用semop函数时需要注意以下几点:

在调用semop之前,需要先通过semget函数获取信号量集的标识符。
要确保对信号量的操作是原子的,即在一个进程执行操作期间,其他进程不能对该信号量进行操作。这可以通过设置合适的标志位或使用信号量相关的函数(如sem_wait和sem_post)来实现。
要注意信号量的初始化和释放。在使用完信号量后,应该使用semctl函数释放相关的资源。
要注意错误处理。在调用semop函数后,应该检查返回值以确定操作是否成功,并根据需要处理错误情况。

总之,semop函数是Linux系统下用于操作信号量的重要函数之一,它可以用于实现进程间的同步和协调。通过合理地使用信号量,可以有效地解决并发访问共享资源时的竞争条件问题。

semctl()

semctl函数是Linux系统下的一个系统调用函数,用于控制信号量集。信号量集是一种用于管理进程间同步和互斥的数据结构。

函数原型如下:

#include <sys/ipc.h>  
#include <sys/sem.h>  int semctl(int semid, int semnum, int cmd, union semun arg);

参数说明:

semid:信号量集的唯一标识符,通常通过semget函数获取。
semnum:指定要操作的信号量在信号量集中的编号(从0开始)。
cmd:指定要执行的操作命令。常见的命令包括:
——GETVAL:获取信号量的当前值。
——SETVAL:将信号量的值设置为指定值。
——GETPID:获取前一个对此信号进行操作的进程的标识符。
——GETNCNT:等待信号值增加的进程的总数。
——GETZCNT:等待信号值变为0的进程的总数。
——SETALL:将所有semun结构中的值设定到信号集中。
arg:根据命令的不同,可以是一个不同的参数结构体。例如,当命令为SETVAL时,需要传递一个包含要设置的值的长整型参数结构体。当命令为GETALL时,需要传递一个用于存储所有信号值的数组的指针。

返回值:如果函数执行成功,返回0;如果执行失败,返回-1并设置相应的错误码。

使用semctl函数时需要注意以下几点:

在调用semctl之前,需要先通过semget函数获取信号量集的标识符。
要确保对信号量的操作是原子的,即在一个进程执行操作期间,其他进程不能对该信号量进行操作。这可以通过设置合适的标志位或使用信号量相关的函数(如sem_wait和sem_post)来实现。
要注意信号量的初始化和释放。在使用完信号量后,应该使用semctl函数释放相关的资源。
要注意错误处理。在调用semctl函数后,应该检查返回值以确定操作是否成功,并根据需要处理错误情况。

总之,semctl函数是Linux系统下用于控制信号量的函数之一,它可以用于获取和设置信号量的值、获取和设置进程信息等操作。通过合理地使用信号量,可以有效地管理进程间的同步和互斥行为。

共享内存

共享内存(Shared Memory)是Linux下进程间通信(Inter-process Communication, IPC)的一种方式,它允许多个进程共享一段物理内存,以实现快速的数据交换和协同工作。

在Linux系统中,每个进程都有自己的虚拟地址空间,通过地址映射机制,将虚拟地址转换为物理内存的地址。共享内存就是将一段物理内存区域定义为共享区域,多个进程可以将其虚拟地址空间映射到这个共享区域,从而可以访问和修改共享区域的数据。

共享内存的创建和管理由操作系统完成。当一个进程需要使用共享内存时,它会向操作系统请求并获得一段共享内存,然后将其映射到自己的虚拟地址空间中。其他进程也可以将共享内存映射到自己的虚拟地址空间,从而可以访问和修改共享内存中的数据。

共享内存的优点在于其访问速度非常快,因为通信时可以直接访问内存。相较于管道等其他IPC方式,共享内存避免了数据在用户空间和内核空间之间的复制和拷贝,提高了数据传输的效率。但是,共享内存也存在一些缺点,例如不支持阻塞等待操作,可能会导致死锁等问题。此外,共享内存的使用也需要谨慎,因为多个进程可以同时访问和修改共享区域的数据,需要有一定的数据同步和互斥机制来保证数据的正确性和一致性。

总之,共享内存是一种高效的进程间通信方式,适用于需要快速、高效地交换大量数据的场景,如数据库、消息队列等应用中。在使用共享内存时,需要注意数据的同步和互斥问题,避免出现死锁和数据不一致等问题。

shmget()

shmget函数是Linux系统下用于创建或获取共享内存段的函数。共享内存是一种进程间通信机制,它允许多个进程共享同一段物理内存,以实现快速的数据交换和协同工作。

函数原型如下:

#include <sys/ipc.h>  
#include <sys/shm.h>  int shmget(key_t key, size_t size, int shmflg);

参数说明:

key:指定共享内存段的键值,通常由ftok函数生成。
size:指定共享内存段的大小,以字节为单位。
shmflg:指定共享内存段的标志位,包括如下选项:
——IPC_CREAT:如果指定的键值不存在,则创建共享内存段。
——IPC_EXCL:如果指定了IPC_CREAT,并且指定的键值已经存在,则返回错误。
——0:指定共享内存段的权限位,例如0600表示只有所有者可以读写。

返回值:如果函数执行成功,返回共享内存段的标识符(即共享内存段的ID);如果执行失败,返回-1并设置相应的错误码。

使用shmget函数时需要注意以下几点:

共享内存段的生命周期与进程无关,当最后一个引用该共享内存段的进程退出时,共享内存段将被自动删除。因此,在使用完共享内存段后,应该通过shmdt函数将其从当前进程的地址空间中解除映射。
共享内存段的访问权限由其标志位指定,可以通过修改标志位来控制对共享内存段的访问权限。例如,可以通过设置0600来限制只有所有者可以读写共享内存段。
在使用共享内存段时,需要确保多个进程之间的同步和互斥,以避免出现竞争条件和死锁等问题。可以通过信号量或其他同步机制来实现进程之间的同步和互斥。
共享内存段的大小受限于系统的限制和可用内存资源。因此,在使用时需要注意合理地分配和管理内存资源。

总之,shmget函数是Linux系统下用于创建或获取共享内存段的函数,它提供了高效的进程间通信机制,适用于需要快速、高效地交换大量数据的场景,如数据库、消息队列等应用中。在使用时需要注意同步和互斥问题,以及合理地分配和管理内存资源。

shmop()

注意这个使用man手册来查的时候,必须用man shmop的方式来查询,用man shmat是没有用的。

Linux系统下并没有shmop函数,而是使用shmat函数来实现共享内存的映射。shmat函数用于将共享内存段映射到调用进程的地址空间中,使得进程可以通过指针访问共享内存中的数据。

shmat函数的原型如下:

#include <sys/ipc.h>  
#include <sys/shm.h>  int shmat(int shmid, const void *shmaddr, int shmflg);

参数说明:

shmid:指定共享内存段的标识符,即通过shmget函数获取的标识符。
shmaddr:指定共享内存段的地址,如果为NULL,则由系统自动分配地址。
shmflg:指定共享内存的访问权限和附加标志。

返回值:如果函数执行成功,返回一个指向映射区域的指针;如果执行失败,返回-1并设置errno来指示具体的错误原因。

使用shmat函数时需要注意以下几点:

调用进程必须具有足够的权限才能访问共享内存段。
映射区域的大小必须与共享内存段的大小相匹配,否则会导致访问越界。
当进程不再需要访问共享内存时,需要使用shmdt函数将映射区域解除映射,以释放资源。
在多进程环境下,需要注意同步和互斥问题,以避免数据竞争和不一致性的情况发生。

总之,shmat函数是Linux系统下用于将共享内存映射到进程地址空间的函数之一,通过映射操作,进程可以通过指针访问共享内存中的数据,实现进程间的数据共享和通信。

除了上述这个函数,还有一个函数shmdt;

shmdt函数是Linux系统下用于将共享内存段从当前进程的地址空间中解除映射的函数。它的原型如下:

#include <sys/types.h>  
#include <sys/shm.h>  int shmdt(const void *shmaddr);

参数说明:

shmaddr:一个指向共享内存段的指针,该指针通常是由shmat函数返回的地址。

返回值:如果函数执行成功,返回0;如果执行失败,返回-1并设置errno来指示具体的错误原因。

shmdt函数的作用是将共享内存段从当前进程的地址空间中分离,相当于shmat函数的反操作。当进程不再需要访问共享内存段时,可以调用shmdt函数将其解除映射,以释放相应的资源。

需要注意的是,shmdt函数仅仅是将共享内存段从当前进程的地址空间中分离,并不会销毁共享内存段本身。共享内存段仍然存在于系统中,直到显式删除或系统重启。

在使用shmdt函数时,需要注意确保进程不再需要访问共享内存段,以免出现意外的问题。同时,在多进程环境下,需要注意同步和互斥问题,以避免数据竞争和不一致性的情况发生。

shmctl()

shmctl函数是Linux系统下用于控制共享内存段的函数。通过shmctl函数,我们可以执行各种操作来控制共享内存段,包括删除共享内存段、修改共享内存段的属性等。

函数原型如下:

#include <sys/ipc.h>  
#include <sys/shm.h>  int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

shmid:指定共享内存段的ID,即通过shmget函数获取的标识符。
cmd:指定要执行的控制命令,用于指定要执行的操作,比如删除共享内存段、修改权限等。常用的命令包括IPC_RMID(删除共享内存段)和IPC_SET(修改共享内存段的属性)等。
buf:指向一个shmid_ds结构体的指针,用于存储共享内存段的信息。可以为NULL,表示不获取共享内存段的信息。

返回值:如果函数执行成功,返回值为0;如果出现错误,返回值为-1,并设置errno来指示具体的错误原因。

shmctl函数可以执行的控制命令包括:

IPC_RMID:删除共享内存段。当使用完共享内存段后,应该通过shmctl函数将其删除,以释放内存资源。执行该命令时,需要将buf参数设置为NULL。
IPC_SET:修改共享内存段的属性。可以通过该命令来修改共享内存段的标志位、拥有者和权限等属性。执行该命令时,需要将buf参数指向一个shmid_ds结构体,并设置相应的字段来修改共享内存段的属性。
IPC_STAT:获取共享内存段的属性。可以通过该命令来获取共享内存段的详细信息,包括创建时间、最后一次修改时间、访问时间等。执行该命令时,需要将buf参数指向一个shmid_ds结构体,并获取共享内存段的信息。

使用shmctl函数时需要注意以下几点:

在删除共享内存段时,需要注意确保没有任何进程正在使用该共享内存段,否则会导致删除失败。
在修改共享内存段的属性时,需要注意权限问题。只有具有相应权限的进程才能修改共享内存段的属性。
在使用完共享内存段后,需要通过shmdt函数将其从当前进程的地址空间中解除映射,以释放共享内存段所占用的资源。
在多进程环境下,需要注意同步和互斥问题,以避免出现竞争条件和死锁等问题。可以通过信号量或其他同步机制来实现进程之间的同步和互斥。

实例

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>#define MEMSIZE 1024int main(){pid_t pid;int shmid;//拿到相同的共享内存的key值//但这里因为是父子进程之间//所以不需要使用ftok来保证多个进程之间使用的是相同//的共享内存实例//ftok();//首先创建一个共享内存的实例//第一个参数表示共享内存实例的key值,这里没有,所以我们用IPC_PRIVATE表示是匿名实例//第二个参数是要设置的共享内存大小//第三个参数表示设置共享内存实例的权限shmid = shmget(IPC_PRIVATE,MEMSIZE,0600);if(shmid < 0){perror("shmget()");exit(1);}//使用父子进程来实现共享内存的使用pid = fork();if(pid < 0){perror("fork()");exit(1);}if(pid == 0){ //子进程写 共享内存数据//使用shmat来映射这块共享内存实例到程序中来//第一个参数是共享内存实例的id号//第二个参数是映射到哪儿去,为NULL表示由系统自动分配//第三个参数是设置一些特殊要求,为0表示没有void* ptr = shmat(shmid,NULL,0);if(ptr == (void *)-1){//手册上是这么写的,就按照手册写perror("shmat()");exit(1);}//映射上来之后就可以往映射到的共享内存实例内开始写数据strcpy(ptr,"Hello!");//用完就解除映射shmdt(ptr);exit(0);}else{ //父进程读 共享内存数据//收尸wait(NULL);void* ptr = shmat(shmid,NULL,0);if(ptr == (void *)-1){perror("shmat()");exit(1);}puts(ptr);//解除映射shmdt(ptr);//销毁共享内存实例shmctl(shmid,IPC_RMID,NULL);exit(0);}exit(0);
}

编译运行:
在这里插入图片描述

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

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

相关文章

Docker部署开源分布式任务调度平台DolphinScheduler并实现远程访问办公

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

12.9_黑马数据结构与算法笔记Java

目录 057 多路递归 e03 杨辉三角2 thinking&#xff1a;二维数组的动态初始化&#xff1f; 057 多路递归 e03 杨辉三角3 058 链表 e01 反转单向链表1 058 链表 e01 反转单向链表2 058 链表 e01 反转单向链表3 递归 058 链表 e01 反转单向链表4 为什么是returnn1呢&…

【Cisco Packet Tracer】路由器 NAT实验

NAT的实现方式有三种&#xff0c;即静态转换Static Nat、动态转换Dynamic Nat和端口多路复用OverLoad。 静态转换是指内部本地地址一对一转换成内部全局地址&#xff0c;相当内部本地的每一台PC都绑定了一个全局地址。一般用于在内网中对外提供服务的服务器。 [3] 动态转换是指…

改进YOLOv8注意力系列一:结合ACmix、Biformer、BAM注意力机制

🗝️改进YOLOv8注意力系列一:结合ACmix、Biformer、BAM注意力机制 代码ACmixBiFormerBAMBlock加入方法各种yaml加入结构本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代码和包含多种更有效加入YOLOv8中的yaml结构,读者可以获…

Mysql索引一篇就够了

索引 定义 索引是对数据库表中一列或者多列的值进行排序的结构。 目的 数据库索引好比一本书的目录&#xff0c;提高查询效率。但是为表设置索引要付出相应的代价&#xff1a; 增加了数据库的存储空间 在插入和修改时需花费更多的时间&#xff08;因为索引也要随之变动&#…

C++相关闲碎记录(5)

1、容器提供的类型 2、Array Array大小固定&#xff0c;只允许替换元素的值&#xff0c;不能增加或者移除元素改变大小。Array是一种有序集合&#xff0c;支持随机访问。 std::array<int, 4> x; //elements of x have undefined value std::array<int, 5> x {…

渗透测试——七、网站漏洞——命令注入和跨站请求伪造(CSRF)

渗透测试 一、命令注入二、跨站请求伪造(CSRF)三、命令注入页面之注人测试四、CSRF页面之请求伪造测试 一、命令注入 命令注入(命令执行) 漏洞是指在网页代码中有时需要调用一些执行系统命令的函数例如 system()、exec()、shell_exec()、eval()、passthru()&#xff0c;代码未…

基于ssm在线云音乐系统的设计与实现论文

摘 要 随着移动互联网时代的发展&#xff0c;网络的使用越来越普及&#xff0c;用户在获取和存储信息方面也会有激动人心的时刻。音乐也将慢慢融入人们的生活中。影响和改变我们的生活。随着当今各种流行音乐的流行&#xff0c;人们在日常生活中经常会用到的就是在线云音乐系统…

走迷宫(详细分析)

目录 一、课题描述 输入样例&#xff1a; 输出样例&#xff1a; 二、需求分析 输入的形式和输入值的范围&#xff1a; 输出的形式&#xff1a; 程序所能达到的功能&#xff1a; 三、概要设计 四、流程图 五 、代码详细注释 六、测试数据和结果 一、课题描述 以一个…

freeswitch webrtc video_demo客户端进行MCU的视频会议

系统环境 一、编译服务器和加载模块 二、下载编译指定版本video_demo 三、配置verto.conf.xml 1.修改配置文件 2.重新启动 四、MCU通话测试 1.如何使用video_demo 2.测试结果 五、MCU的通话原理及音频/视频/布局/管理员等参数配置 附录 freeswitch微信交流群 系统环境 lsb_rel…

lv11 嵌入式开发 IIC(下) 20

目录 1 Exynos4412下IIC控制器介绍 1.1 总览 1.2 特征 1.3 工作框图 1.4 其他内容介绍 1.5 四种工作模式寄存器流程 2 IIC寄存器详解 2.1 概述 2.2 控制寄存器 2.3 状态寄存器 2.4 地址寄存器 2.5 数据寄存器 2.6 其他寄存器 3 MPU06050 3.1 简介 3.2 MPU6050主…

HJ103 Redraiment的走法

题目&#xff1a; HJ103 Redraiment的走法 题解&#xff1a; dfs 暴力搜索 枚举数组元素&#xff0c;作为起点如果后续节点大于当前节点&#xff0c;继续向后搜索记录每个起点的结果&#xff0c;求出最大值 public int getLongestSub(int[] arr) {int max 0;for (int i 0…

data_loader返回的每个batch的数据大小是怎么计算得到的?

data_loader是一个通用的术语&#xff0c;用于表示数据加载器或数据批次生成器。它是在机器学习和深度学习中常用的一个概念。 一、data loader 数据加载器&#xff08;data loader&#xff09;是一个用于加载和处理数据集的工具&#xff0c;它可以将数据集划分为小批次&#…

内存学习——堆(heap)

目录 一、概念二、自定义malloc函数三、Debug运行四、heap_4简单分析4.1 heap管理链表结构体4.2 堆初始化4.3 malloc使用4.4 free使用 一、概念 内存分为堆和栈两部分&#xff1a; 栈&#xff08;Stack&#xff09;是一种后进先出&#xff08;LIFO&#xff09;的数据结构&…

AVFormatContext封装层:理论与实战

文章目录 前言一、封装格式简介1、FFmpeg 中的封装格式2、查看 FFmpeg 支持的封装格式 二、API 介绍三、 实战 1&#xff1a;解封装1、原理讲解2、示例源码 13、运行结果 14、示例源码 25、运行结果 2 四、 实战 2&#xff1a;转封装1、原理讲解2、示例源码3、运行结果 前言 A…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑电力-交通交互的配电网故障下电动汽车充电演化特性》

这个标题涉及到电力系统、交通系统和电动汽车充电的复杂主题。让我们逐步解读&#xff1a; 考虑电力-交通交互的配电网故障&#xff1a; 电力-交通交互&#xff1a; 指的是电力系统和交通系统之间相互影响、相互关联的关系。这可能涉及到电力需求对交通流量的影响&#xff0c;反…

回溯算法之N皇后

一 什么是回溯算法 回溯算法&#xff08;Backtracking Algorithm&#xff09;是一种用于解决组合优化问题的算法&#xff0c;它通过逐步构建候选解并进行验证&#xff0c;以寻找所有满足特定条件的解。回溯算法通常应用于在给定约束条件下枚举所有可能解的问题&#xff0c;如…

Git—文件添加查看删除修改

目录 1.添加文件—场景一 2.查看.git文件 3.添加文件—场景三 4.修改文件 5.版本回退 6.撤销修改 7.删除文件 1.添加文件—场景一 在包含.git的目录下新建⼀个ReadMe文件&#xff0c;我们可以使用 git add 命令可以将文件添加到暂存 区&#xff1a; ●添加一个或多个文…

Matlab数学建模算法之小波神经网络详解

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 &#x1f510;#### 防伪水印——左手の明天 ####&#x1f510; &#x1f497; 大家…

存储成本降71%,怪兽充电历史库迁移OceanBase

怪兽充电作为共享充电宝第一股&#xff0c;业务增长迅速&#xff0c;以至于业务架构不停地增加组件。在验证 OceanBase 可以简化架构并带来更大的业务价值后&#xff0c;首次尝试在历史库中使用 OceanBase 替代 MySQL&#xff0c;存储成本降低 71%。本文为怪兽充电运维架构部王…