APUE学习之进程间通信(IPC)(下篇)

目录

一、进程间通信(IPC)    

二、信号量(Semaphore)

1、基本概念

2、同步关系与互斥关系

3、临界区与临界资源

4、信号量的工作原理

5、信号量编程

6、实战演练

 三、共享内存(Shared Memory)

1、基本概念

2、共享内存的优点

3、共享内存的缺点

4、共享内存编程

5、实战演练

 四、消息队列(Message Queue)

1、基本概念

2、特点和用途

3、消息队列编程

4、实战演练

五、学习心得


一、进程间通信(IPC)    

        在计算机编程和操作系统中,进程间通信(Inter-Process Communication,IPC)是实现不同进程之间数据传输和共享资源的关键技术。在多任务和多进程系统中,各个进程可能需要相互通信以协调任务、共享数据或进行同步操作。本文将深入探讨几种常见的进程间通信方式,如下图所示:

         本篇文章将会详细讲解进程间通信的三种常见方法:信号量、共享内存、消息队列。如果有同学对于另外四种方法不是很熟悉,推荐看这篇文章---《APUE学习之进程间通信(IPC)(上篇)》,这篇文章详细讲解了进程间通信的另外四种常见方法:信号、管道、命名管道、命名socket

二、信号量(Semaphore)

1、基本概念

        在《APUE学习之进程间通信(IPC)(上篇)》这篇文章中我们讲到了信号(Signal),大家不要混淆二者。信号是使用信号处理器来进行的,而信号量是使用PV操作来实现的。

        信号量主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。每个执行流在进入临界区之前都应该先申请信号量,申请成功就有了操作特点的临界资源的权限,当操作完毕后就应该释放信号量。流程如下:

       信号量就是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作。信号量值(sem_id)指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。

        在学习信号量前,大家先一起了解几个概念。

2、同步关系与互斥关系

(1) 同步关系

        同步关系指的是多个线程或进程之间通过某种机制协调执行顺序,以达到特定的目的或保证数据的一致性。在同步关系中,各个线程或进程之间可能会互相等待、协作或交换信息,以实现某种有序的执行流程。(实现先执行A,在执行B)

(2)互斥关系

        互斥关系指的是多个线程或进程之间通过某种机制保证对临界资源的互斥访问,即同一时间只能有一个线程或进程可以访问共享资源,以避免数据竞争和数据一致性问题。(A和B都想干同一件事,但同一时间只能有一个人干)

(3)区别

        同步关系侧重于协调多个线程或进程的执行顺序和操作,以实现特定的目的或保证数据的一致性;而互斥关系侧重于保证对临界资源的互斥访问,避免数据竞争和数据不一致性问题。

3、临界区与临界资源

(1)临界区(Critical Section)

        临界区是指一段代码,当多个线程或进程同时执行这段代码时,可能会导致竞态条件或数据不一致的情况发生。因此,需要确保在任何时刻,最多只有一个线程或进程可以进入临界区执行代码,以保证数据的正确性和一致性。

        在实际编程中,通过使用同步原语(如互斥锁、信号量等)来保护临界区,使得每次只有一个线程或进程可以获得访问权限,从而避免了竞态条件的发生。

(2)临界资源(Critical Resource)

        临界资源是指需要被临界区代码段保护的共享资源,可能是内存、文件、数据库连接、硬件设备等。多个线程或进程需要对这些资源进行访问,但同时只能有一个线程或进程进行访问,否则可能会导致数据损坏、不一致性等问题。

        对于临界资源,需要确保在对其进行访问时,只有一个线程或进程可以进行操作,以防止数据竞争和不一致性。

        注意:临界区是指需要互斥访问的代码段,而临界资源是指需要被保护的共享资源。这就好比A和B一起去卫生间,但是卫生间只有一个坑位,也就是卫生间同一时间只允许一个人进入。在这个情境下,卫生间就属于临界区,而坑位则属于临界资源。

4、信号量的工作原理

        由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

(1)P操作(等待)

        P 操作用于请求资源或进入临界区,它会检查信号量的值。如果信号量的值大于 0,则将其减 1,并允许线程或进程继续执行,表示资源已被占用。如果信号量的值为 0,则线程或进程将被阻塞,直到信号量的值变为大于 0 为止。

(2)V操作(释放)

        V 操作用于释放资源或退出临界区,它会增加信号量的值。当线程或进程完成对资源的访问时,执行 V 操作将信号量的值加 1,表示资源已被释放。如果有其他线程或进程正在等待资源,则执行 V 操作后会唤醒其中一个等待的线程或进程,使其可以继续执行。

(3)PV 操作与信号量的关系

        多个执行流为了访问临界资源会竞争式的申请信号量, 因此信号量是会被多个执行流同时访问的,也就是说信号量本质也是临界资源

        信号量本质就是用于保护临界资源的,我们不可能再用信号量去保护信号量,所以信号量的PV操作必须是原子操作

5、信号量编程

        在Linux系统中,我们如果想要使用信号量进行编程,需要用到下面四个函数,让我们一起来看看吧!

(1)ftok()函数

        ftok()用来创建IPC键的函数,通常用于在多个进程间共享 IPC 对象时生成唯一的键值。无论是信号量、共享内存还是消息队列,都需要一个key_t类型的关键字ID值。该函数是将文件的索引节点号(可用ls -i进行查看)取出, 前面加上子序号即可得到key_t的返回值。

        让我们一起来看看函数原型吧:

#include   <sys/types.h>

#include   <sys/ipc.h>

 

key_t ftok(const char *pathname, int proj_id);      

参数说明:

(1)第一个参数pathname:一个指向路径名的字符串,会使用该文件的文件索引号。【注意:指向的文件必须存在】

(2)第二个参数proj_id:用户指定的一个子序号,取值范围是1~255 。

(3)返回值:如果成功,返回一个与 pathnameproj_id 相关联的唯一 IPC 键。如果失败,则返回-1 。 

(2)semget()函数

        该函数用来创建一个信号集,或者获取已经存在的信号集。让我们一起来看看函数原型吧:

#include    <sys/types.h>

#include    <sys/ipc.h>

#include    <sys/sem.h>

 

int semget(key_t key, int num_sems, int sem_flags);

参数说明:

(1)第一个参数key:即ftok()的返回值。

(2)第二个参数num_sems:指定要创建的信号量集中信号量的数量,即信号量集中包含的信号量个数。通常为1 。

(3)第三个参数sem_flags:指定信号量集的权限标志和创建/打开标志,通常使用 IPC_CREAT。设置 IPC_CREAT后,即使给出的键是一个已有信号量的键,也不会产生错误。

(4)返回值:成功返回信号集的标识符,失败返回-1 。

(3)semctl()函数

        该函数用来初始化信号集,或者删除信号集。让我们一起来看看函数的原型吧:

#include   <sys/types.h>

#include   <sys/ipc.h>

#include   <sys/sem.h>

 

int semctl(int semid, int semnum, int cmd, ...);

参数说明:

(1)第一个参数semid:semget()返回的信号量集标识符。

(2)第二个参数semnum:一个整数值,用于指定要操作的信号量在信号量集中的索引(从0开始)。

(3)第三个参数cmd:一个整数值,用于指定要执行的操作类型,可以是以下之一:

GETVAL获取指定信号量的值
SETVAL设置指定信号量的值,此时需传入第四个参数
IPC_RMID删除指定的信号量集

(4)第四个参数是可选的,如果使用该参数,则其类型是semun,它是多个特定命令参数的联合(union),该联合不在任何系统头文件中定义,需要我们自己在代码中定义:

union        semun

{

        int                        val;

        struct semid_ds  *buf;

        unsigned short    *array;

        struct seminfo     *__buf;

};

(5)返回值:根据操作类型的不同返回相应的一个正数。失败则返回-1 。

        注意:使用 IPC_RMID 命令删除信号量集时,需要确保该信号量集中的所有资源都不再被使用,否则可能导致资源泄漏或其他错误。

(4)semop()函数

        该函数操作一个或者一组信号,也可以叫做PV操作。让我们先来看看函数原型吧:

#include   <sys/types.h>

#include   <sys/ipc.h>

#include   <sys/sem.h>

 

int semop(int semid, struct sembuf *sops, size_t nsops);

参数说明:

(1) 第一个参数semid:semget()返回的信号量集标识符。

(2)第二个参数sops:其指向一个信号量操作数组。信号量操作由结构体sembuf结构表示如下:

struct sembuf {
    short sem_num;          // 信号量在信号量集中的索引
    short sem_op;           // 对信号量的操作值

                                     //操作为负则是P操作,操作为正则是V操作
    short sem_flg;          // 操作标志,通常为 IPC_NOWAIT 或 SEM_UNDO
};

【IPC_NOWAIT 表示非阻塞操作,或 SEM_UNDO 表示在进程结束时撤销未完成的操作 ,避免程序在异常情况下结束时未解锁锁定的资源,造成资源被永远锁定(死锁)】

(3)第三个参数nsops:信号操作结构的数量,恒大于或等于1 。

6、实战演练

题目:

        编写一个程序,通过PV操作来实现父子进程同步运行。要求如下:在初始化信号量时将信号量初始值设为0,如果父进程先运行的话,将会调用semaphore_p(semid),这时因为资源为0所以父进程会阻塞。而之后子进程运行时会执行semaphore_v(semid)将资源+1,父进程之后就可以运行了。

代码如下:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>#define FTOK_PATH       "/dev/zero"
#define FTOK_PROJID     0x22union semun
{int                     val;struct semid_ds         *buf;unsigned short          *arry;
};int semaphore_init(void);
int semaphore_p(int semid);
int semaphore_v(int semid);
void semaphore_term(int semid);int main(int argc,char *argv[])
{int             semid;pid_t           pid;int             i;if((semid = semaphore_init()) < 0){printf("semaphore initial failure:%s\n",strerror(errno));return -1;}if((pid = fork()) < 0){printf("fork() failure:%s\n",strerror(errno));return -2;}else if(0 == pid)       /*child process*/{printf("Child process start running and do something now...\n");sleep(3);printf("Child process do something over...\n");semaphore_v(semid);sleep(1);printf("Child process exit now\n");exit(0);}/*Parent process*//*前面的semaphore_init()函数里将信号量的值设为0,如果这时候父进程先执行的话,p操作会阻塞。直到子进程执行V操作后,父进程的P操作才能返回继续执行*/printf("Parent process P operator wait child process over\n");semaphore_p(semid);printf("Parent process start to run...\n");sleep(1);printf("Parent process do something over...\n");printf("Parent process destroy samaphore and exit\n");semaphore_term(semid);sleep(2);return 0;
}int semaphore_init(void)
{key_t           key;int             semid;union semun     sem_union;if((key = ftok(FTOK_PATH,FTOK_PROJID)) < 0){printf("ftok() get IPC token failure:%s\n",strerror(errno));return -1;}semid = semget(key,1,IPC_CREAT|0664);if(semid < 0){printf("semget() get semid failure:%s\n",strerror(errno));return -2;}sem_union.val = 0;if(semctl(semid,0,SETVAL,sem_union) < 0){printf("semctl() set initial value failure:%s\n",strerror(errno));return -3;}printf("Semaphore get key_t[0x%x] and semid[%d]\n",key,semid);return semid;
}int semaphore_p(int semid)
{struct sembuf   _sembuf;_sembuf.sem_num = 0;_sembuf.sem_op  = -1;_sembuf.sem_flg= SEM_UNDO;if(semop(semid,&_sembuf,1) < 0){printf("semop p operator failure:%s\n",strerror(errno));return -1;}return 0;
}int semaphore_v(int semid)
{struct sembuf   _sembuf;_sembuf.sem_num = 0;_sembuf.sem_op  = 1;_sembuf.sem_flg= SEM_UNDO;if(semop(semid,&_sembuf,1) < 0){printf("semop v operator failure:%s\n",strerror(errno));return -1;}return 0;
}void semaphore_term(int semid)
{union semun     sem_union;if(semctl(semid,0,IPC_RMID,sem_union) < 0){printf("semctl() delete semaphore ID failure:%s\n",strerror(errno));}return ;
}

运行结果:

 三、共享内存(Shared Memory)

1、基本概念

        共享内存本质上就是内存中的一块区域,用于进程间通信使用。该内存空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据块,同时还会创建相应结构体来记录该共享内存属性,以便于管理。因此,共享内存不只有一份,可以根据需求申请多个。进程之间进行通信的时候,会获取 到共享内存的地址,写端进程写入数据,读端进程通过直接访问内存完成数据读取。

2、共享内存的优点

(1)高效性共享内存是最快的进程间通信方式,一旦这样的内存映射到共享它的进程的地址空间,进程不再执行进入内核的系统调用来传递彼此的数据。

(2)灵活性:共享内存提供了灵活的数据共享方式,进程可以自由地读写共享内存中的数据,而无需进行额外的同步和通信操作。

(3)适用于大数据量:对于大数据量的数据共享,共享内存是一种比较合适的选择,因为它不需要将数据复制到其他进程的地址空间,节省了内存和 CPU 资源。

(4)支持随机访问:共享内存允许进程对内存区域进行随机访问,可以快速定位和访问需要的数据,适用于需要频繁访问的情况。

3、共享内存的缺点

(1)进程同步:由于多个进程可以同时访问共享内存,因此需要额外的同步机制来保证数据的一致性和正确性,如信号量、互斥锁等。

(2)数据一致性:共享内存中的数据可能会被多个进程同时修改,需要仔细考虑数据一致性和同步的问题,避免数据竞争和不一致性。

(3)安全性:共享内存的安全性受到进程权限控制的限制,如果不加以限制和保护,可能会导致安全漏洞或数据泄露的风险。

(4)可移植性:共享内存的实现和使用在不同的操作系统和平台上可能存在差异,需要考虑其可移植性和兼容性问题。

(5)复杂性:使用共享内存进行进程间通信可能会增加程序的复杂性,因为需要处理并发访问和同步的问题,编写和维护相应的同步代码较为复杂。

4、共享内存编程

        在Linux系统下,如果想要使用共享内存进行编程,需要用到下面五个函数,其中ftok()已在上文讲过,所以下面着重讲解其他四个函数:

(1)shmget()函数

        该函数用来创建共享内存,让我们先来看看函数原型吧:

#include <sys/ipc.h>
#include <sys/shm.h>

 

int shmget(key_t key, size_t size, int shmflg);

参数说明:

(1)第一个参数key:即ftok()的返回值。

(2)第二个参数size:指定了共享内存段的大小(以字节为单位)。

(3)第三个参数shmflg:一个整数值,用于指定共享内存段的创建方式和权限标志,通常使用 IPC_CREAT 。

(4)返回值:成功返回该共享内存段的标识码,失败则返回-1 。

(2)shmat()函数

        该函数用于将共享内存段连接到调用进程的地址空间,使得进程可以访问共享内存中的数据。让我们一起来看看函数原型吧:

#include <sys/types.h>
#include <sys/shm.h>

 

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数说明:

(1)第一个参数shmid:shmget()返回的共享内存标识符。

(2)第二个参数shmaddr:一个指向共享内存的地址,指定共享内存连接到当前进程中的地址位置,通常为 NULL,表示由系统自动选择合适的地址。

(3)第三个参数shmflg:一个整数值,用于指定共享内存的连接方式和权限标志,通常为 0。

(4)返回值:成功时返回一个指向共享内存第一个字节的指针,失败则返回-1 。

【注意:连接共享内存后,应该谨慎地使用指向共享内存的指针,确保不会越界访问共享内存以避免导致未定义行为。】

(3)shmdt()函数

        该函数用于将共享内存段从调用进程的地址空间中分离,即断开进程与共享内存段之间的连接,防止内存泄漏。让我们一起来看看函数原型吧:

#include <sys/types.h>
#include <sys/shm.h>

 

int shmdt(const void *shmaddr);

参数说明:

(1) 参数shmaddr:一个指向共享内存段的指针,即连接到进程地址空间的共享内存的起始地址。也就是shmat()函数的返回值。

(2)返回值:调用成功返回0 ,失败则返回-1 。

【注意:该函数只是将连接到当前进程地址空间的共享内存段进行分离,使得进程无法再访问该共享内存段中的数据。但共享内存段本身不会被删除,其他仍连接的进程仍然可以访问共享内存。】

(4)shmctl()函数

         该函数用于对共享内存段进行控制操作,包括获取共享内存段的状态信息、设置共享内存段的权限和删除共享内存段等。让我们一起来看看函数原型吧:

#include <sys/types.h>
#include <sys/shm.h>

 

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

(1)第一个参数shmid:shmget()返回的共享内存标识符。

(2)第二个参数cmd:一个整数值,用于指定要执行的操作类型,可以是以下之一:

IPC_STAT获取共享内存段的状态信息,将其存储在 buf 参数中。
IPC_SET设置共享内存段的权限和状态信息,通过 buf 参数传递要设置的值。
IPC_RMID删除指定的共享内存段。

(3)第三个参数buf:一个指向 shmid_ds 结构体的指针,用于存储获取到的共享内存段的状态信息,或者传递要设置的共享内存段的权限和状态信息。结构体定义如下:

struct        shmid_ds

{

        uid_t        shm_perm.uid;

        uid_t        shm_perm.gid;

        mode_t    shm_perm.mode;

}

(4)返回值:如果成功,根据操作类型的不同返回相应的值。如果失败则返回-1 。

【注意:删除共享内存段时,需要确保所有连接到该共享内存段的进程都已经分离并且不再需要该共享内存段,否则可能导致数据丢失或内存泄漏。】 

5、实战演练

题目:

        编写一个程序,让两个进程使用共享内存方式共享一个结构体变量。要求如下:

(1)编写一个程序(write.c)用来创建一个student结构体共享内存并更新里面的成员内容。

(2)编写一个程序(read.c)在另一个毫无关系的进程中同步访问该结构体里的内容。

代码如下:

write.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define FTOK_PATH       "/dev/zero"
#define FTOK_PROJID     0x22typedef struct  st_student
{char            name[64];int             age;
}t_student;int main(int argc,char *argv[])
{key_t           key;int             shmid;int             i;t_student       *student;if((key = ftok(FTOK_PATH,FTOK_PROJID)) < 0){printf("ftok() get IPC token failure:%s\n",strerror(errno));return -1;}shmid = shmget(key,sizeof(t_student),IPC_CREAT|0666);if(shmid < 0){printf("shmget() create shared memory failure:%s\n",strerror(errno));return -2;}student = shmat(shmid,NULL,0);if((void *)-1 == student){printf("shmat() alloc shared memory failure:%s\n",strerror(errno));return -2;}strncpy(student->name,"xinhongbo",sizeof(student->name));student->age = 18;for(i=0 ; i<4 ; i++){student->age ++;printf("Student '%s' age [%d]\n",student->name,student->age);sleep(1);}shmdt(student);shmctl(shmid,IPC_RMID,NULL);return 0;
}

read.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define FTOK_PATH       "/dev/zero"
#define FTOK_PROJID     0x22typedef struct  st_student
{char    name[64];int     age;
}t_student;int main(int argc,char *argv[])
{key_t           key;int             shmid;int             i;t_student       *student;if((key = ftok(FTOK_PATH,FTOK_PROJID)) < 0){printf("ftok() get IPC token failure:%s\n",strerror(errno));return -1;}shmid = shmget(key,sizeof(t_student),IPC_CREAT|0666);if(shmid < 0){printf("shmget() create shared memroy failure:%s\n",strerror(errno));return -2;}student = shmat(shmid,NULL,0);if((void *)-1 == student){printf("shmat() alloc shared memroy failure:%s\n",strerror(errno));return -2;}for(i=0 ; i<4 ; i++){printf("Student '%s' age [%d]\n",student->name,student->age);sleep(1);}shmdt(student);shmctl(shmid,IPC_RMID,NULL);return 0;
}

运行结果:

 四、消息队列(Message Queue)

1、基本概念

        消息队列一般简称为 MQ ,是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,是在消息的传输过程中保存消息的容器。消息队列本质上是一个队列,而队列中存放的是一个个消息。

        消息队列是一种在多个进程间进行通信的机制,它是一种先进先出(FIFO)的数据结构,用于在不同的进程之间传递消息。消息队列通常由操作系统内核维护,提供了一种可靠的、异步的进程间通信方式。

        MQ传递的是消息,消息即是我们需要需要在进程间传递的数据。MQ采用链表来实现消息队列,该链表是由系统内核来维护。每个MQ用消息队列描述符(消息队列ID:qid)来区分,qid是唯一的,用来区分不同的MQ。在进行进程间通信时,一个进程将消息加到MQ尾端,另一个进程从消息队列中取消息(不一定以先进先出来取消息),这样就实现了进程间的通信。

2、特点和用途

(1)异步通信: 消息队列允许发送者和接收者之间进行异步通信,即发送者发送消息后可以立即继续执行其他任务,而无需等待接收者的响应。

(2)可靠性: 消息队列通常由操作系统内核维护,确保消息传递的可靠性和正确性。即使发送者和接收者不在同一时间活动,消息也能够安全地传递和存储,直到接收者准备好接收。

(3)缓冲: 消息队列通常具有一定的缓冲能力,允许发送者发送消息的速率大于接收者接收消息的速率,从而平衡系统的吞吐量。

(4)解耦: 使用消息队列可以将发送者和接收者解耦,使它们之间的通信变得灵活和独立。发送者只需将消息发送到队列,而无需知道具体的接收者是谁,接收者也只需从队列中获取消息,而无需知道消息的来源。

(5)多对多通信: 消息队列支持多对多的通信模式,即多个发送者可以向同一个队列发送消息,多个接收者也可以从同一个队列接收消息。

3、消息队列编程

        在Linux系统下,我们如果想要使用消息队列进行编程,需要用到如下的五个函数。其中ftok()函数已在上文讲过,这里着重讲解其余四个函数。

(1)msgget()函数

        函数是用于创建或打开一个消息队列、创建消息队列ID的函数。消息队列是一种在多个进程之间进行通信的机制,它提供了一个先进先出(FIFO)的缓冲区,用于在不同的进程之间传递消息。让我们一起来看看函数原型吧:

 #include <sys/types.h>
 #include <sys/ipc.h>
#include <sys/msg.h>
 

int msgget(key_t key, int msgflg);

参数说明:

(1)第一个参数key: 即ftok()的返回值。

(2)第二个参数msgflg:一个整数值,用于指定消息队列的创建方式和权限标志,通常使用 IPC_CREAT 。不存在则根据 key 参数创建一个新的消息队列。,存在则返回其标识符。

(3)返回值:如果成功返回一个非负整数的消息队列标识符,如果失败则返回-1 。

(2)msgsnd()函数

        函数用于向消息队列发送消息,前提是要有写消息队列的权限。让我们先来看看函数的原型吧:

 #include <sys/types.h>
 #include <sys/ipc.h>
#include <sys/msg.h>

 

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数说明:

(1)第一个参数msqid:由 msgget() 返回的消息队列标识符。

(2)第二个参数msgp:一个指向消息缓冲区的指针,包含要发送的消息内容。消息结构在两方面受到制约。首先,它必须小于系统规定的上限值;其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型,其参考类型定义形式如下:

typedef  struct        s_msgbuf

{
        long        mtype;

        char        mtext[512];

}t_msgbuf;

(3)第三个参数msgsz:指定了要发送消息的大小(以字节为单位)。

(4)第四个参数msgflg: 控制着当前消息队列满或到达系统上限时要发生的事情,设置为IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。

(5)返回值:成功返回0,失败返回-1 。

【注意:如果发送的消息大小超过了消息队列的限制,将会导致发送失败并返回错误。】

(3)msgrcv()函数

        该函数用于从消息队列接收消息。让我们先来看看函数的原型吧:

 #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);

参数说明:

(1)第一个参数 msqid:由 msgget() 返回的消息队列标识符。

(2)第二个参数msgp:一个指向消息缓冲区的指针,指向准备接受的消息,用于存储接收到的消息内容。

(3)第三个参数msgsz:指定了消息缓冲区的长度,即可接收的最大消息大小。(这个长度不包含保存消息类型的那个long int长整型)

(4)第四个参数msgtyp:一个长整型值,用于指定要接收的消息类型。

msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息

(5)第五个参数msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事。

msgflg=IPC_NOWAIT队列中没有可读消息不等待,返回ENOMSG错误
msgflg=MSG_NOERROR消息大小超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT接收类型不等于msgtype的第一条消息

(6)返回值:如果成功,返回接收到的消息的字节数。如果失败则返回-1 。

(4)msgctl()函数

        该函数用于对消息队列执行控制操作,包括获取消息队列的状态信息、设置消息队列的权限和删除消息队列等。让我们先来看看函数原型吧:

 #include <sys/types.h>
 #include <sys/ipc.h>
#include <sys/msg.h>

 

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数说明:

(1)第一个参数 msqid:由 msgget() 返回的消息队列标识符。

(2)第二个参数cmd:用于指定要执行的操作类型。

IPC_STAT把msqid_ds结构体中的数据设置为消息队列的当前关联值
IPC_SET如果进程有足够的权限,就把消息队列的当前关联值设置为msqid_ds结构中给出的值
IPC_RMID删除消息队列

(3)第三个参数buf:一个指向 msqid_ds 结构体的指针,用于存储获取到的消息队列的状态信息,或者传递要设置的消息队列的权限和状态信息。

(4)返回值:如果成功,根据操作类型的不同返回相应的值。如果出错,返回 -1 。

4、实战演练

题目:

        编写一个程序,实现不同进程通过消息队列来实现收发消息。要求如下:

(1)编写一个程序,往内核的消息队列里写入内容“ping”。

(2)编写一个程序,从消息队列里读出并打印该消息。

send.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>#define FTOK_PATH       "/dev/zero"
#define FTOK_PROJID     0x22typedef struct s_msgbuf
{long            mtype;char            mtext[512];
}t_msgbuf;int main(int argc,char *argv[])
{key_t           key;int             msgid;t_msgbuf        msgbuf;int             msgtype;int             i;if((key = ftok(FTOK_PATH,FTOK_PROJID)) < 0){printf("ftok() get IPC token failure:%s\n",strerror(errno));return -1;}msgid = msgget(key,IPC_CREAT|0666);if(msgid < 0){printf("shmget() create share memroy failure:%s\n",strerror(errno));return -2;}msgtype = (int)key;printf("key[%d] msgid[%d] msytype[%d]\n",(int)key,msgid,msgtype);for(i=0 ; i<4 ; i++){msgbuf.mtype = msgtype;strcpy(msgbuf.mtext,"Ping");if(msgsnd(msgid,&msgbuf,sizeof(msgbuf.mtext),IPC_NOWAIT) < 0){printf("msgsnd() send message failure:%s\n",strerror(errno));break;}printf("Send message:%s\n",msgbuf.mtext);sleep(2);}msgctl(msgid,IPC_RMID,NULL);return 0;
}

recver.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>#define FTOK_PATH       "/dev/zero"
#define FTOK_PROJID     0x22typedef struct  s_msgbuf
{long            mtype;char            mtext[512];
}t_msgbuf;int main(int argc,char *argv[])
{key_t           key;int             msgid;t_msgbuf        msgbuf;int             msgtype;int             i;if((key = ftok(FTOK_PATH,FTOK_PROJID)) < 0){printf("ftok() get IPC token failure:%s\n",strerror(errno));return -1;}msgid = msgget(key,IPC_CREAT|0666);if(msgid < 0){printf("shmget() create shared memroy failure:%s\n",strerror(errno));return -2;}msgtype = (int)key;printf("key[%d] msgid[%d] msgtype[%d]\n",(int)key,msgid,msgtype);for(i=0 ; i<4 ; i++){memset(&msgbuf,0,sizeof(msgbuf));if(msgrcv(msgid,&msgbuf,sizeof(msgbuf.mtext),msgtype,IPC_NOWAIT) < 0){printf("msgsnd() receive message failure:%s\n",strerror(errno));break;}printf("Recive Message:%s\n",msgbuf.mtext);sleep(2);}msgctl(msgid,IPC_RMID,NULL);return 0;
}

运行结果:

五、学习心得

        学习进程间通信(IPC)是操作系统和并发编程中的重要主题之一,它涉及多种技术和机制,包括信号、管道、命名管道、命名socket、消息队列、共享内存、信号量等。下面是我对学习进程间通信的一些心得体会:

1. 理解通信需求:

        在学习进程间通信之前,首先要理解为什么需要进程间通信以及不同场景下的通信需求。通信需求可能涉及数据交换、资源共享、同步和协作等方面。

2. 掌握通信方式:

        学习进程间通信需要掌握不同的通信方式和机制,包括信号、管道、命名管道、命名socket、消息队列、共享内存、信号量等。每种通信方式都有其特点和适用场景,理解它们的原理和使用方法对解决特定问题非常重要。

3. 深入理解进程和线程:

         进程间通信通常是在多个进程之间进行的,因此深入理解进程的概念、进程的生命周期以及进程间的关系对理解通信机制非常有帮助。同时,对于多线程编程也应有一定的了解,因为线程间通信也是一种常见的通信方式。

4. 注意通信原理:

        学习进程间通信时要注意理解通信的原理和底层实现机制,例如操作系统是如何管理进程和线程、如何实现进程间数据传输和同步等。深入理解这些原理有助于更好地应用和调优通信机制。

5. 实践和应用:

        学习进程间通信不仅需要理论知识,更需要实践和应用。通过编写实际的程序来使用不同的通信方式,解决实际的问题,才能更好地掌握和理解进程间通信的技术和技巧。

6.注意安全和稳定性:

         在实际应用中,要注意进程间通信的安全性和稳定性。例如要处理好并发访问和竞争条件,避免死锁等问题,确保通信过程的安全可靠。

7. 持续学习和探索:

        进程间通信是一个广阔而复杂的领域,随着技术的发展和需求的变化,不断涌现出新的通信方式和机制。因此,要保持持续学习和探索的态度,关注最新的进展和技术,不断丰富和提升自己的知识和技能。

        总的来说,学习进程间通信是一项极具挑战性但也非常有意义的任务。通过不断学习、实践和探索,可以更好地理解和应用进程间通信的技术,为解决实际问题提供更好的解决方案。

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

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

相关文章

如何使用 Maltego 情报调查保姆级教程(附链接)

前言 使用软件需要挂梯子 一、介绍 Maltego 是一种开放源代码的情报和数据连接工具&#xff0c;专注于网络情报收集和图形化分析。它为用户提供了一个交互式的界面&#xff0c;用于收集、分析和可视化有关目标的信息。Maltego 被广泛用于网络侦查、威胁情报、渗透测试和安全…

Windows、Linux、Mac数据库的安装(mysql、MongoDB、Redis)

数据库的安装 作为数据存储的重要部分&#xff0c;数据库同样是必不可少的&#xff0c;数据库可以分为关系型数据库和非关系型数据库。 关系型数据库如 SQLite、MySQL、Oracle、SQL Server、DB2 等&#xff0c;其数据库是以表的形式存储&#xff1b;非关系型数据库如 MongoDB…

我用Rust开发Rocketmq name server

我是蚂蚁背大象(Apache EventMesh PMC&Committer)&#xff0c;文章对你有帮助给Rocketmq-rust star,关注我GitHub:mxsm&#xff0c;文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsmapache.com 1. Rocketmq-rust namesrv概述 经过一个多月的开发&#xff0c;终…

【web安全】文件上传漏洞

upload-labs靶场 第一关 绕过前端 先打开哥斯拉&#xff0c;生成木马&#xff0c;选择php 打开brup开浏览器&#xff0c;上传文件&#xff0c;就会发现被阻止了&#xff0c;还没抓到包呢 那就是被前端代码阻止了&#xff0c;那通常前端代码都只能防御后缀名 我们抓到包后直…

php+Layui开发的网站信息探针查询源码

信息探针是一款基于layui开发的专业查询好友个人信息的程序。 自定义设置探针页面&#xff0c;探针功能&#xff0c;QQ分享&#xff0c;通知邮箱等功能。 生成页面链接好友点击会出现好友ip 位置信息&#xff0c;手机型号ua头浏览器等信息 gps需要注册百度地图开发者才可以使用…

盒子模型的内容总结

知识引入 1.认识盒子模型 在浏览网站时我们会发现内容都是按照区域划分的。这使得网页很工整、美观。在页面中&#xff0c;每一块区域分别承载不同的内容&#xff0c;使得网页的内容虽然零散&#xff0c;但是在版式排列上依然清晰有条理。如图1 图1 *承载内容的区域称为盒子…

数字护盾:深度探讨Sentinel的三大流控策略

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 数字护盾&#xff1a;深度探讨Sentinel的三大流控策略 前言快速失败策略&#xff1a;数字守卫的拦截术快速失败策略的基本原理&#xff1a;示例场景演示&#xff1a; Warm Up策略&#xff1a;数字城堡…

如何安装配置HFS并实现无公网ip远程访问本地电脑共享文件

文章目录 前言1.下载安装cpolar1.1 设置HFS访客1.2 虚拟文件系统 2. 使用cpolar建立一条内网穿透数据隧道2.1 保留隧道2.2 隧道名称2.3 成功使用cpolar创建二级子域名访问本地hfs 总结 前言 在大厂的云存储产品热度下降后&#xff0c;私人的NAS热度快速上升&#xff0c;其中最…

HiveSQL题——窗口函数(lag/lead)

目录 一、窗口函数的知识点 1.1 窗户函数的定义 1.2 窗户函数的语法 1.3 窗口函数分类 1.4 前后函数:lag/lead 二、实际案例 2.1 股票的波峰波谷 0 问题描述 1 数据准备 2 数据分析 3 小结 2.2 前后列转换&#xff08;面试题&#xff09; 0 问题描述 1 数据准备 …

Pytest中doctests的测试方法应用

在 Python 的测试生态中,Pytest 提供了多种灵活且强大的测试工具。其中,doctests 是一种独特而直观的测试方法,通过直接从文档注释中提取和执行测试用例,确保代码示例的正确性。本文将深入介绍 Pytest 中 doctests 的测试方法,包括基本用法和实际案例,以帮助你更好地利用…

Habitat环境学习一:导航任务概述

导航任务 概述&#xff1a; Agent需要导航到环境中的指定位置&#xff0c;而该位置可以由特定的目标形式指定。 PointGoal Navigation&#xff1a;导航到环境中相对于初始位置特定的点&#xff1b; Object Navigation&#xff1a;Agent需要在环境中找到特定的物体&#xff1…

【C++】类和对象(一)

前言&#xff1a;在前面我们带大家初步步入了C&#xff0c;让大家大概知道了他的样子&#xff0c;那今天就可以说我们要正式步入C的大门了&#xff0c;这一章内容的细节比较多各位学习的时候一定要仔细。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f…

leetcode 27.移除元素(python版)

需求 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度…

如何多好一个FMEA——SunFMEA软件

尽管FMEA的理念和流程相对简单&#xff0c;但在实际操作中&#xff0c;许多企业发现FMEA的实施并不容易&#xff0c;往往无法达到预期的效果。今天SunFMEA软件和大家一起 SunFMEA软件 首先&#xff0c;FMEA的实施需要跨部门、跨领域的团队合作。然而&#xff0c;在很多企业中&a…

RX-8564 LC实时时钟模块

.内置 32.768 kHz 晶体单元(频率精度调整完毕) .接口类型&#xff1a;I2C-Bus 接口 (400 kHz) .工作电压范围&#xff1a;1.8 V ~ 5.5 V .计时&#xff08;保持&#xff09;电压范围 &#xff1a;1.0 V ~ 5.5 V / -20 ˚C ~70 ˚C .低待机电流 &#xff1a;275 nA / 3.0…

Acrel-2000MG微电网能量管理系统在工商业储能行业中的应用

一、概述: 在新型电力系统中新能源装机容量逐年提高&#xff0c;但是新能源比如光伏发电、风力发电是不稳定的能源&#xff0c;所以要维持电网稳定&#xff0c;促进新能源发电的消纳&#xff0c;储能将成为至关重要的一环&#xff0c;是分布式光伏、风电等新能源消纳以及电网安…

超强预测模型:二次分解-组合预测

1 长时间序列预测模型Informer Informer是一种基于自注意力机制的序列预测模型&#xff0c;专门用于时间序列预测任务,在时间序列预测领域取得了显著的性能Informer主要特点&#xff1a; 注意力机制与多层编码器-解码器结构 Informer模型引入了全局自注意力和局部自注意力机…

Linux操作系统权限相关问题(一站式速通权限)

一、sudo命令 sudo yum install -y sl sudo命令的作用 不切换用户&#xff0c;就想让普通用户以root的身份&#xff0c;执行对应的指令 输入密码时&#xff0c;输入的是自己普通用户的密码&#xff0c;而不是root的密码&#xff01;&#xff01;&#xff01; sudo可以进行…

移动端深度编辑产品技术解决方案

视频编辑已经成为企业宣传、教育、娱乐等多个领域的重要工具。美摄科技凭借其深厚的技术积累和对市场需求的敏锐洞察&#xff0c;开发出业界领先的移动端深度编辑产品&#xff0c;为企业提供高效、专业的视频编辑解决方案。 美摄科技移动端深度编辑产品方案&#xff0c;基于多…

那些年与指针的情仇(二)---二级指针指针与数组的那点事函数指针

关注小庄 顿顿解馋(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e; 欢迎回到我们的大型纪录片《那些年与指针的爱恨情仇》&#xff0c;在本篇博客中我们将继续了解指针的小秘密&#xff1a;二级指针&#xff0c;指针与数组的关系以及函数指针。请放心食用&a…