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;
	//先拿到key
	key = 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);
	}
	
	//销毁消息队列实例
	//第一个参数是要进行控制的消息队列实例
	//第二个参数是要进行的操作命令
	//第三个参数是第二个参数操作的传参,这里没有,写NULL
	msgctl(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;
	//先拿到key
	key = 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 1024

int 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);
}

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

  • 23
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在地球迷路的怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值