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     0x22

union 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     0x22

typedef 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     0x22

typedef 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     0x22

typedef 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     0x22

typedef 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. 持续学习和探索:

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程c++

目录 1.join和detach区别 2.lock_guard和unique_lock 3.原子操作 4.条件变量condition_variable 5.future 和 promise 1.join和detach区别 ①不使用join和detach #include <iostream> #include <thread> #include <windows.h>using namespace std;v…

Linux文本三剑客-grep

1.grep简介&#xff1a; grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具&#xff0c;它能使用正则表达式搜索文本&#xff0c;并把匹配的行打印出来&#xff0c;都是按行处理的。 grep 最主要…

腾讯云云监控实践:使用云审计 CloudAudit SDK 精准管理腾讯云资源

文章目录 一、什么是腾讯云的操作审计 CloudAudit二、CloudAudit 有哪些优势三、CloudAudit 应用场景举例3.1 安全分析3.2 资源变更跟踪3.3 合规性审计 四、使用云审计 SDK 进行云监控4.1 安装环境包 PHP4.2 下载并解压云审计 PHP SDK4.3 创建的腾讯云持久证书&#xff08;如果…

解决:ModuleNotFoundError: No module named ‘selenium’

解决&#xff1a;ModuleNotFoundError: No module named ‘selenium’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named selenium背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff0c;直接安装方法二&#xff0c;手动下载安装方法三&#xff0…

【Tomcat与网络2】一文理解Servlet是怎么工作的

在前面&#xff0c;我们研究了如何用idea来启动一个Servlet程序&#xff0c;今天我们就再来看一下Servlet是如何工作的。 目录 1.Servlet 介绍 2.Servlet 容器工作过程 3.Servlet的扩展 不管是电脑还是手机浏览器&#xff0c;发给服务端的就是一个 HTTP 格式的请求&#xf…

摄像头提示sd卡未格式化怎么回事?怎么解决

作为摄像头用户&#xff0c;往往会遇到或多或少的技术问题。而当摄像头显示"SD卡未格式化"的提示时&#xff0c;这可能令一些用户感到困惑和担忧。在本文中&#xff0c;我们将解释这个提示的原因&#xff0c;并提供一些建议来解决这一问题。我们相信本文会让您更加了…

select的change方法如何传递多个参数

element-ui中select的change方法传递多个参数 element-ui中的select&#xff0c;checkbox等组件的change方法的回调函数只有当前选择的val&#xff0c;如果想再传入自定义参数怎么办&#xff1f; 不能够传入自定义的参数&#xff0c;在进行某些操作时&#xff0c;会比较困难&…

[设计模式Java实现附plantuml源码~结构型]对象的间接访问——代理模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

Ubuntu18.04安装Matlab流程笔记

提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 Ubuntu18.04 安装Matlab流程 下载安装包和破解文件安装Matlab注册并运行 下载安装包和破解文件 matlabR2019A源码 提取码:2ztb 下载的Linux matlab2018a文件夹内有三个文件&#xff1a; # 解压Matlab201…

深度学习之循环神经网络 (基础)

循环神经网络简称为RNN&#xff0c;&#xff08;之前讲到的卷积神经网络简称为CNN&#xff09;。 以前我们在使用全链接网络的时候&#xff0c;我们将这种网络叫做Dense 或者是Deep。 Dense链接指的是全链接的。 我们输入的数据是数据样本的不同特征&#xff1a;x1&#xff…

VScode设置行宽提示线

vscode 设置行宽提示线&#xff0c;可以按如下步骤设置&#xff1a; 打开设置 搜索框输入 rulers&#xff0c;选择用户&#xff0c;点击 在 settings.json 中编辑 跳转到一个 json 文件后&#xff0c;将字段 rulers 对应值设置为 80 补充&#xff1a;如果您在您的 json 配…

JUC并发编程-四大函数式接口、Stream 流式计算、ForkJoin并行执行任务

12. 四大函数式接口 新时代的程序员&#xff1a;lambda表达式、链式编程、函数式接口、Stream流式计算 函数式接口&#xff1a;只有一个方法的接口&#xff0c;可以有一些默认的方法 如&#xff1a;Runnable接口函数 1&#xff09;Function 函数型接口 public class Functio…

java 图书管理系统 spring boot项目

java 图书管理系统ssm框架 spring boot项目 功能有管理员模块&#xff1a;图书管理&#xff0c;读者管理&#xff0c;借阅管理&#xff0c;登录&#xff0c;修改密码 读者端&#xff1a;可查看图书信息&#xff0c;借阅记录&#xff0c;登录&#xff0c;修改密码 技术&#…

常用芯片学习——CD4094芯片

CD4094 8位移位寄存器/3态输出缓冲器 使用说明 CD4094是由一个 8 位串行移位寄存器和一个 3 态输出缓冲器组成的 CMOS 集成电路。寄存器带有存储锁存功能&#xff0c;集成电路根据 STROBE 信号确定锁存器是否接收移位寄存器各位数据&#xff0c;数据是否由锁存器传输到 3 态输…

在Windows上安装与配置Apache服务并结合内网穿透工具实现公网远程访问本地内网服务

文章目录 前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpolar web ui管理界面3.2 创建公网地址 4. 固定公网地址 前言 Apache作为全球使用较高的Web服务器…

如何通过Hive/tez与Hadoop的整合快速实现大数据开发

一、Hive的功能 Hive是基于Hadoop的一个外围数据仓库分析组件&#xff0c;可以把Hive理解为一个数据仓库&#xff0c;但这和传统的数据库是有差别的。 传统数据库是面向业务存储&#xff0c;比如 OA、ERP 等系统使用的数据库&#xff0c;而数据仓库是为分析数据而设计的。同时…

移动Web-动画

1、动画-animation 过渡&#xff1a;实现两个状态间的变化过程 动画&#xff1a;实现多个状态间的变化过程&#xff0c;动画过程可控&#xff08;重复播放、最终画面、是否暂停&#xff09; 1.1 实现步骤 1.1.1 定义动画 1.1.2 使用动画 <!DOCTYPE html> <html lang…

备战蓝桥杯---数据结构与STL应用(入门2)

话不多说&#xff0c;直接看题&#xff1a; 前4个操作我们可以用deque解决&#xff0c;第5个我们不用真的去反转&#xff0c;调换一下头尾即可。 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,m,k,k1,ee0; bool cm1(int a,int b){retur…

C++ 数论相关题目 博弈论:拆分-Nim游戏

给定 n 堆石子&#xff0c;两位玩家轮流操作&#xff0c;每次操作可以取走其中的一堆石子&#xff0c;然后放入两堆规模更小的石子&#xff08;新堆规模可以为 0 &#xff0c;且两个新堆的石子总数可以大于取走的那堆石子数&#xff09;&#xff0c;最后无法进行操作的人视为失…

网络安全从入门到精通(超详细)学习路线

首先看一下学网络安全有什么好处&#xff1a; 1、可以学习计算机方面的知识 在正式学习网络安全之前是一定要学习计算机基础知识的。只要把网络安全认真的学透了&#xff0c;那么计算机基础知识是没有任何问题的&#xff0c;操作系统、网络架构、网站容器、数据库、前端后端等…