系列文章:

  • 文件操作
  • 数据管理
  • 进程和信号
  • POSIX 线程
  • 进程间通信:管道
  • 信号量共享内存和消息队列
  • 套接字

文章目录

  • 1. 信号量
    • 1.1 信号量的定义
    • 1.2 Linux 的信号量机制
    • 1.3 使用信号量
  • 2. 共享内存
    • 2.1 shmget 函数
    • 2.2 shmat 函数
    • 2.3 shmdt 函数
    • 2.4 shmctl 函数
  • 3. 消息队列
    • 3.1 msgget 函数
    • 3.2 msgsnd 函数
    • 3.3 msgrcv 函数
    • 3.4 msgctl 函数

本章将讨论一组进程间通信的机制,由于这些机制都出现在同一个版本中并且有着相似的编程接口,所以它们常被称为IPC(Inter-Process Communication,进程间通信)机制。

  • 信号量:用于管理对资源的访问。
  • 共享内存:用于在程序之间高效地共享数据。
  • 消息队列:在程序之间传递数据的一种简单方法。

1. 信号量

当我们编写的程序使用了线程时,不管它是运行在多用户系统上、多进程系统上,还是运行在多用户多进程系统上,我们通常会发现,程序中存在着一部分临界代码,我们需要确保只有一个进程可以进入这个临界代码并拥有对资源独占式的访问权。

为了防止出现因多个程序同时访问一个共享资源而引发的问题,我们需要有一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域,我们可以在使用线程的程序中通过互斥量或信号量来控制对临界区的访问。

要想编写通用的代码,以确保程序对某个特定的资源具有独占式的访问权是非常困难的。虽然有一个名为 Dekker 算法的解决方法,但这个算法依赖于忙等待或自旋锁,也就是说,一个进程要持续不断地以等待某个内存位置被改变,在像 Linux 这样的多任务环境中,人们不愿意使用这种浪费 CPU 资源的处理方法,但如果硬件支持独占式访问(一般是通过特定的 CPU 指令的形式),那么情况就变得简单多了。一个硬件支持的例子就是,用一条指令以原子方式访问并增加寄存器的值,在这个读取/增加/写入操作执行的过程中不会有其他指令(甚至一个中断)发生。

一种可能的解决方法是,使用带有 O_EXCL 标志的 open 函数来创建锁文件,它提供了原子化的文件创建方法。它允许一个进程通过获取一个令牌来取得成功,这个方法比较适合处理简单问题,但对于更加复杂的例子,它就显得比较杂乱且缺乏效率。

荷兰科学家 Dijkstra 提出的信号量概念是在并发编程领域迈出的重要一步,信号量是一个特殊的变量,它只取整数值,并且程序对其访问都是原子操作,只允许对它进行等待(P,passeren,wait)和发送信号(V,vrijgeven,signal)两种操作。

1.1 信号量的定义

最简单的信号量是只能取值 0 和 1 的变量,即二进制信号量。PV 操作的定义非常简单,假设有一个信号量变量 sv,则这两个操作的定义如表:

P(sv): 如果 sv 的值大于零,就给它减去 1;如果它的值等于零,就挂起该进程。
V(sv): 如果有其它进程因为等待 sv 而被挂起,就让它恢复运行,如果没有进程因等待 sv 而被挂起,就给它加 1。

注意,只用一个普通变量进行类似的加减法是不行的,因为在 C、C++、C#或几乎任何一个传统的编程语言中,都没有一个原子操作可以满足检测变量是否为true,如果是再将变量设置为false的需要,这也是信号量操作如此特殊的原因。

1.2 Linux 的信号量机制

Linux 系统中的信号量接口经过了精心设计,它提供了比通常所需要更多的机制,所有的 Linux 信号量函数都是针对成组的通用信号量进行操作,而不是只针对一个二进制信号量。

#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command,....);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

这些函数都是用来对成组的信号量进行操作的,这使得对它们的操作要比单个信号量所需要的操作复杂的多。

semget 函数

该函数的作用是创建一个新信号量或取得一个已有信号量的键:

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

第一个参数 key 是整数值,不相干的进程可以通过它访问同一个信号量,程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个相应的信号量标识符。只有 semget 函数才直接使用信号量键,所有其它的信号量函数都是使用由 semget 函数返回的信号量标志符。

有一个特殊的信号量键值 IPC_PRIVATE,它的作用是创建一个只有创建者进程才可以访问的信号量,但这个键值很少有实际的用途。在创建新的信号量时,你需要给键提供一个唯一的非零整数。

num_sems,指定信号量数目,它几乎总是取值为 1。

sem_flags 参数是一组标志,它与 open 函数的标志非常相似,它低端的 9 个比特是该信号量的权限,其作用类似于文件的访问权限。此外,它们还可以和值 IPC_CREAT 按位或操作,来创建一个新信号量。我们可以通过联合使用标志 IPC_CREAT 和 IPC_EXCL 来确保创建出的是一个新的、唯一的信号量。如果该信号量已存在,它将返回一个错误。

semop 函数

该函数用于改变信号量的值。

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

第一个参数 sem_id 是由 semget 返回的信号量标识符,第二个参数是指向一个结构数组的指针,每个数组元素至少包含以下几个成员:

struct sembuf{short sem_num; // 信号量编号,除非需要使用一组信号量,否则取值一般是 0short sem_op、; // 信号量在一次操作中需要改变的数值。通常只会用到两个值,一个是-1,一个是+1short sem_flg; //  通常被设置为 sem_UNDO,使得操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。除非你对信号量的行为有特殊的要求,否则应该养成设置 sem_flg 为 SEM_UNDO 的好习惯。
}

semop 调用的一切动作都是一次性完成的,这是为了避免出现因使用多个信号量而可能发生的竞争现象。

semctl 函数

该函数用来直接控制信号量信息。

int semctl(int sem_id, int sem_num, int command,....);

第一个参数 sem_id 是由 semget 返回的信号量标识符,sem_num 参数是信号量编号,当需要用到成组的信号量时,就要用到这个参数,它一般取值为 0,表示这是一个也是唯一一个信号量。command 参数是将要采取的动作。如果还有第四个参数,它将会是一个 union semun 结构,至少包含以下几个成员:

union semun{int val;struct semid_ds *buf;unsigned short *array;
}

虽然 command 参数可以设置许多不同的值,但只有下面介绍的两个值最常用:

  • SETVAL: 用来把信号量初始化为一个已知的值。这个值通过 union semun 中的 val 成员设置,其作用是在信号量第一次使用之前对它进行设置。
  • IPC_RMID: 用于删除一个已经无需继续使用的信号量标识符。

1.3 使用信号量

大部分需要使用信号量来解决的问题只需要一个最简单的二进制信号量即可,在下面的例子中,将用完整的编程接口为二进制信号量创建一个简单得多的 PV 类型的接口,然后用这个接口演示信号量是如何工作的。

我们用 seml.c 来试验信号量,该程序可以被多次调用,我们通过一个可选的参数来指定程序是负责创建信号量还是负责删除信号量。

我们用两个不同字符的输出来表示进入和离开临界区域,如果程序启动时带有一个参数,它将在进入和退出临界区时打印字符x,而程序的其他运行示例将在进入和退出临界区时打印字符 O,因为在任一给定时刻,只能有一个进程可以进入临界区域,所以字符X和O应该是成对出现的。

jiaming@jiaming-pc:~/Documents/test$ cc seml.c -o seml
jiaming@jiaming-pc:~/Documents/test$ cat run.sh
./seml 1 &
./seml
jiaming@jiaming-pc:~/Documents/test$ ./run.sh
OOOOXXOOXXOOXXOOXXOOXXOOOOOOXXOOX
8711 - finished
jiaming@jiaming-pc:~/Documents/test$ XXXXXXX
8710 - finished
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>union semun
{int val;struct semid_ds *buf;unsigned short *arry;
};static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
// 调用semget来创建一个信号量,该函数将返回一个信号量标识符,如果程序是第一个被调用的,就调用 set_semvalue 初始化信号量,并将 op_char 设置为 X
int main(int argc, char *argv[])
{int i;int pause_time;char op_char = 'O';srand((unsigned int)getpid());sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT); // 通过一个随机选取的键来取得一个信号量标识符,IPC_CREAT: 如果信号量不存在,就创建它if(argc > 1){if(!set_semvalue()){fprintf(stderr, "Failed to initialize semaphore\n");exit(EXIT_FAILURE);}op_char = 'X';sleep(2);}// 接下来是一个循环,它进入和离开临界区域10次,在每次循环的开始,首先调用semaphore_p函数,它在程序将进入临界区域时设置信号量以等待进入:for(i = 0; i < 10; i++){if(!semaphore_p()) exit(EXIT_FAILURE);printf("%c", op_char);fflush(stdout);pause_time = rand() % 3;sleep(pause_time);printf("%c", op_char);fflush(stdout);// 在临界区之后,调用semaphore_v来将信号量设置为可用,然后等待一段随机的时间,再进入下一次循环,在整个循环语句执行完毕后,调用del_semavalue函数来清理代码if(!semaphore_v()) exit(EXIT_FAILURE);pause_time = rand() % 2;sleep(pause_time);}printf("\n%d - finished\n", getpid());if(argc > 1){sleep(10);del_semvalue();}exit(EXIT_SUCCESS);
}// 函数 set_semvalue 通过将 semctl 调用的 command 参数设置为 SETVAL 来初始化信号量,在使用信号量之前必须这样做。
static int set_semvalue(void)
{union semun sem_union;sem_union.val = 1;if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return 0;return 1;
}
static void del_semvalue(void)
{union semun sem_union;if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)fprintf(stderr, "Failed to delete semaphore\n");
}
static int semaphore_p(void)
{struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = -1; // Psem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){fprintf(stderr, "semaphore_p failed\n");return 0;}return 1;
}
static int semaphore_v(void)
{struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = 1; // Vsem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){fprintf(stderr, "semaphore_v failed\n");return 0;}return 1;
}

在实际编程中,我们需要特别小心,不要无意之中在执行结束之后还留下信号量未删除,它可能会在你下次运行此程序时引发问题,而且信号量也是一种有限的资源,需要节约使用。

2. 共享内存

共享内存是3个IPC机制中的第二个,它允许两个不相干的进程访问同一逻辑内存,共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。大多数共享内存的具体实现,都把由不同进程之间共享的内存安排为同一段物理内存。

共享内存是由IPC为进程创建的一个特殊的地址范围,它将出现在该进程的地址空间中,其它进程可以将同一段共享内存连接到它们自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样,如果某个进程向共享内存写入了数据,所做的改动将立刻可以访问同一段共享内存的任何其它进程看到。

共享内存为在多个进程之间共享和传递数据提供了一种有效的方式,由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。我们一般使用共享内存来提供对大块内存区域的有效访问,同时通过传递小消息来同步对该内存的访问。

在第一个进程结束对共享内存的写操作之前,并无自动的机制可以阻止第二个进程开始对它进行读取,对共享内存的同步控制必须由程序员负责。

图中箭头显示了每个进程的逻辑地址空间到可用物理内存的映射关系,实际情况要比图中显示的更加复杂,因为可用的内存实际上是由物理内存和已交换到磁盘上的内存页面混合组成的。

共享内存使用的函数类似于信号量的函数,定义如下:

#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t ket, size_t size, int shmflg);

2.1 shmget 函数

作用是创建共享内存,

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

与信号量一样,程序需要提供一个参数 key,它有效地为共享内存段命名,shmget 函数返回一个共享内存标识符,该标识符将用于后续的共享内存函数。有一个特殊的键值 IPC_PRIVATE,它用于创建一个只属于创建进程的共享内存,通常不会用到这个值,在一些Linux系统中,私有的共享内存其实不是真正的私有。

第二个参数 size 以字节为单位指定需要共享的内存容量。

第三个参数shmflg包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志一样。

权限标志对于共享内存非常有用,因为它们允许一个进程创建的共享内存可以被共享内存的创建者所拥有的的进行写入,同时其它用户创建的进程只能读取该共享内存。我们可以利用这个功能来提供一种有效地对数据进行只读访问的方法,通过将数据放入共享内存并设置它的权限,就可以避免数据被其他用户修改。

如果共享内存创建成功,shmget 返回一个非负整数,即共享内存标识符,如果失败,返回 -1。

2.2 shmat 函数

第一次创建共享内存段时,它不能被任何进程访问。要想启用对该共享内存的访问,必须将其连接到第一个进程的地址空间中,这项工作由shmat函数来完成。

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一个参数shm_id是由shmget返回的共享内存标识符。

第二个参数shm_addr指定的是共享内存连接到当前进程中的地址位置,它通常是一个空指针,表示让系统来选择共享内存出现的地址。

第三个参数shmflg是一组位标志,它的两个可能取值是SHM_RND(这个标志与shm_addr联合使用,用来控制共享内存连接的地址)和SHM_RDONLY(使得连接的内存只读),很少需要控制共享内存连接的地址,通常都是让系统来选择一个地址,否则就会使应用程序对硬件的依赖性过高。

如果 shmat 调用成功,它返回一个指向共享内存第一个字节的指针;如果失败,就返回-1。共享内存的访问权限由它的属主(共享内存的创建者)、它的访问权限和当前进程的属主决定,共享内存的访问权限类似于文件的访问权限。

2.3 shmdt 函数

shmdt 函数的作用是将共享内存从当前进程中分离,它的参数是shmat返回的地址指针,成功时返回 0,失败时返回-1。注意,将共享内存分离并未删除它,只是使得该共享内存对当前进程不再可用。

2.4 shmctl 函数

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

shmid_ds 结构至少包含以下成员:

struct shmid_ds {uid_t shm_perm.uid;uid_t shm_perm.gid;mode_t shm_perm.mode;
}

第一个参数 shm_id 是 shmget 返回的共享内存标识符。

第二个参数 command 是要采取的动作,可以取如下三个值:

IPC_STAT: 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET: 如果进程有足够的权限,就把共享内存的当前关联值设置为 shmid_ds 结构中给出的值
IPC_RMID: 删除共享内存段

第三个参数 buf 是一个指针,它指向包含共享内存模式和访问权限的结构。

成功时返回 0,失败时返回 -1。

我们将编写一对程序 shm1.c 和 shm2.c,第一个程序(消费者)将创建一个共享内存段,然后把写到它里面的数据都显示出来,第二个程序(生产者)将连接一个已有的共享内存段,并允许向其中写入数据。

#define TEXT_SZ 2048struct shared_use_st
{int written_by_you; // 当有数据写入这个结构时,我们使用该标志通知消费者char some_char[TEXT_SZ];
};
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
// 消费者
int main(int argc, char *argv[])
{int running = 1;void *shared_memory = (void *)0;struct shared_use_st *shared_stuff;int shmid;srand((unsigned int)getpid());shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);if(shmid == -1){fprintf(stderr, "shmget failed\n");exit(EXIT_FAILURE);}// 让程序可以访问这个共享内存shared_memory = shmat(shmid, (void *)0, 0);if(shared_memory == (void *)-1){fprintf(stderr, "shmat failed\n");exit(EXIT_FAILURE);}printf("Memory attatched at %X\n", (int)shared_memory);// 将 shared_memory分配给shared_stuff,然后它输出written_by_you中的文本,循环一直执行直到找到end字符串为止,sleep调用强迫消费者程序在临界区域多呆一会,让生产者程序等待shared_stuff = (struct shared_use_st*)shared_memory;shared_stuff->written_by_you = 0;while(running){if(shared_stuff->written_by_you){printf("You wrote: %s", shared_stuff->some_char);sleep(rand() % 4);shared_stuff->written_by_you = 0;if(strncmp(shared_stuff->some_char, "end", 3) == 0){running = 0;}}}// 共享内存被分离,被删除if(shmdt(shared_memory) == -1){fprintf(stderr, "shmdt failed\n");exit(EXIT_FAILURE);}if(shmctl(shmid, IPC_RMID, 0) == -1){fprintf(stderr, "shmctl(IPC_RMID) failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);
}
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
// 生产者int main(int argc, char *argv[])
{int running = 1;void *shared_memory = (void *)0;struct shared_use_st *shared_stuff;char buffer[BUFSIZ];int shmid;srand((unsigned int)getpid());shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);if(shmid == -1){fprintf(stderr, "shmget failed\n");exit(EXIT_FAILURE);}// 让程序可以访问这个共享内存shared_memory = shmat(shmid, (void *)0, 0);if(shared_memory == (void *)-1){fprintf(stderr, "shmat failed\n");exit(EXIT_FAILURE);}printf("Memory attatched at %X\n", (int)shared_memory);// 将 shared_memory分配给shared_stuff,然后它输出written_by_you中的文本,循环一直执行直到找到end字符串为止,sleep调用强迫消费者程序在临界区域多呆一会,让生产者程序等待shared_stuff = (struct shared_use_st*)shared_memory;while(running){if(shared_stuff->written_by_you == 1){sleep(1);printf("waiting for cliend...\n");}printf("Enter some text: ");fgets(buffer, BUFSIZ, stdin);strncpy(shared_stuff->some_char, buffer, TEXT_SZ);shared_stuff->written_by_you = 1;if(strncmp(buffer, "end", 3) == 0){running = 0;}}if(shmdt(shared_memory) == -1){fprintf(stderr, "shmdt failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);
}
jiaming@jiaming-pc:~/Documents/test$ ./shm1 &
[1] 12110
jiaming@jiaming-pc:~/Documents/test$ Memory attatched at 5D776000jiaming@jiaming-pc:~/Documents/test$ ./shm2
Memory attatched at D4617000
Enter some text: hello
You wrote: hello
waiting for cliend...
Enter some text: jdisajfifjhg
You wrote: jdisajfifjhg
waiting for cliend...
Enter some text: end
You wrote: end

第二个程序使用相同的键1234来取得并连接同一个共享内存段。

这里使用了非常简陋的同步标志 written_by_you,包括一个非常缺乏效率的忙等待,在实际编程中,我们应该使用信号量或通过传递消息(使用管道或IPC消息)、生成信号的方法来提供应用程序读、写部分之间的一种更有效率的同步机制。

3. 消息队列

消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性,但使用消息队列并未解决我们在使用命名管道时遇到的一些问题,比如管道满时的阻塞问题。

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法,而且,每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块。好消息是,我们可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题,更好的是,我们可以用一些方法来提前查看紧急消息。坏消息是,与管道一样,每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。

Linux 系统有两个宏定义MSGMAX和MSGMNB,分别以字节为单位定义了一条消息的最大长度和一个队列的最大长度。

#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msqid, void *msg_ptr, size_t msg_gz, long int msgtype, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

3.1 msgget 函数

用来创建和访问一个消息队列:

int msgget(key_t key, int msgflg);

与其它IPC机制一样,程序必须提供一个键值来命名某个特定的消息队列,特殊键值IPC_PRIVATE用于创建私有队列,理论上,只能被当前进程访问,在某些linux系统上并非私有。第二个参数 msgflg 由9个权限标志组成,由IPC_CREAT定义的一个特殊位必须和权限标志按位或才能创建一个新的消息队列。在设置IPC_CREAT标志时,如果给出的是一个已有消息队列的键,也不会产生错误。成功返回一个正整数,即队列标识符,否则-1。

3.2 msgsnd 函数

msgsnd 函数用来把消息添加到消息队列中:

int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息的结构受到两方面的约束,首先,它的长度必须小于系统规定的上限,其次,它必须以一个长整型成员变量开始,接收函数将用这个成员变量来确定消息的类型,当使用消息时,最好把消息结构定义为下面这样:

struct my_message{long int message_type;// the data you want to transfer
}

由于在消息的接收中要用到 message_type,必须在声明自己的数据结构时包含它,并且最好将它初始化为一个已知值。

第一个参数是msqid是由msgget函数返回的消息队列标识符。

第二个参数是msg_ptr是一个指向准备发送消息的指针,消息必须像刚才说的那样以一个长整型成员变量开始。

第三个参数 msg_sz 是 msg_ptr 指向的消息的长度,这个长度不能包括长整型消息类型成员变量的长度。

第四个参数msgflg控制在当前消息队列满或队列消息到达系统范围的限制时将要发生e的事情。如果msgflg中设置了IPC_NOWAIT标志,函数将立刻返回,不发送消息并且返回为-1。如果msgflg中的IPC_NOWAIT标志,则发送进程将挂起以等待队列中腾出可用空间。

成功时这个函数返回 0,失败时返回 -1,如果调用成功,消息数据的一份副本将被放到消息队列中。

3.3 msgrcv 函数

该函数从一个消息队列中获取消息,

int msgrcv(int msqid, void *msg_ptr, size_t msg_gz, long int msgtype, int msgflg);

第一个参数 msgid 是由msgget 函数返回的消息队列标识符。

第二个参数 msg_ptr 是一个指向准备接收消息的指针,消息必须像前面 msgsnd 函数中介绍的那样以一个长整型成员变量开始。

第三个参数 msg_gz 是 msg_ptr 指向的消息的长度,它不包括长整型消息类型成员变量的长度。

第四个参数 msgtype 是一个长整数,它可以实现一种简单形式的接收优先级,如果 msgtype 的值为 0,就获取队列中的第一个可用消息,如果它的值大于零,将获取具有相同消息类型的第一个消息,如果它的值小于零,将获取消息类型等于或小于 msgtype 的绝对值的第一个消息。

如果只想按照消息发送的顺序来接收它们,就把 msgtype 设置为 0,如果只想获取某一特定类型的消息,就把 msgtype 设置为相应的类型值,如果想接受类型等于或小于n的消息,就把msgtype设置为-n。

第五个参数 msgflg 用于控制当队列中没有相应类型的消息可以接收时将发生的事情。如果 msgflg 中的 IPC_NOWAIT 标志被设置,函数将会立刻返回,返回值为 -1,如果msgflg 中的 IPC_NOWAIT 标志被清除,进程将会挂起以等待一条相应类型的消息到达。

3.4 msgctl 函数

它的作用与共享内存的控制函数非常相似,

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

struct msqid_ds{uid_t msg_perm.uid; // uid_t msg_perm.gid; mode_t msg_perm.mode;
}

第一个参数是 msgget 返回的消息队列标志符。

第二个参数 command 是将要采取的动作,

IPC_STAT: 把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET: 如果进程有足够的权限,就把消息队列的当前关联值设置为 msqid_ds 结构中给出的值
IPC_RMID: 删除消息队列
jiaming@jiaming-pc:~/Documents/test$ cc msg1.c -o msg1
jiaming@jiaming-pc:~/Documents/test$ cc msg2.c -o msg2
jiaming@jiaming-pc:~/Documents/test$ ./msg2
Enter some text: hello
Enter some text: How are you today?
Enter some text: end
jiaming@jiaming-pc:~/Documents/test$ ./msg1
You wrote: hello
You wrote: How are you today?
You wrote: end
// 发送者
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>#define MAX_TEXT 512struct my_msg_st {long int my_msg_type;char some_text[BUFSIZ];
};int main(int argc, char *argv[])
{int running = 1;int msgid;struct my_msg_st some_data;char buffer[BUFSIZ];// 建立消息队列msgid = msgget((key_t)1234, 0666 | IPC_CREAT);if(msgid == -1){fprintf(stderr, "msgget failed with error: %d\n", errno);exit(EXIT_FAILURE);}// 从消息队列中获取消息,直到遇到endwhile(running){printf("Enter some text: ");fgets(buffer, BUFSIZ, stdin);some_data.my_msg_type = 1;strcpy(some_data.some_text, buffer);if(msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1){fprintf(stderr, "msgsnd failed\n");exit(EXIT_FAILURE);}if(strncmp(buffer, "end", 3) == 0){running = 0;}}exit(EXIT_SUCCESS);
}
// 接收者
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>struct my_msg_st {long int my_msg_type;char some_text[BUFSIZ];
};int main(int argc, char *argv[])
{int running = 1;int msgid;struct my_msg_st some_data;long int msg_to_reveive = 0;// 建立消息队列msgid = msgget((key_t)1234, 0666 | IPC_CREAT);if(msgid == -1){fprintf(stderr, "msgget failed with error: %d\n", errno);exit(EXIT_FAILURE);}// 从消息队列中获取消息,直到遇到endwhile(running){if(msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_reveive, 0) == -1){fprintf(stderr, "msgrcv failed with error: %d\n", errno);exit(EXIT_FAILURE);}printf("You wrote: %s", some_data.some_text);if(strncmp(some_data.some_text, "end", 3) == 0){running = 0;}}if(msgctl(msgid, IPC_RMID, 0) == -1){fprintf(stderr, "msgctl(IPC_RMID) failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);
}

假设消息队列中有空间,发送者就可以创建队列,放一些数据到队列中,然后在接受者启动之前就退出。

【Linux Program】信号量、共享内存和消息队列相关推荐

  1. Linux下进程间通信方式之管道、信号、共享内存、消息队列、信号量、套接字

    /* 1,进程间通信 (IPC ) Inter-Process Communication比较好理解概念的就是进程间通信就是在不同进程之间传播或交换信息.2,linux下IPC机制的分类:管道.信号. ...

  2. ipcs命令详解——共享内存、消息队列、信号量定位利器【转】

    (转自:https://blog.csdn.net/dalongyes/article/details/50616162?utm_medium=distribute.pc_relevant.none- ...

  3. VxWorks中的任务间通信(信号量、共享内存、消息队列、管道、信号、事件、套接字socket)

    文章目录 信号量 共享内存 消息队列 管道 信号 事件 套接字(socket) 总结 VxWorks5.5中,为了保证各个独立的任务可以协同工作,提供了一整套任务间的通信机制,主要包括信号量,共享内存 ...

  4. php 共享内存列队,php中对共享内存,消息队列的操作

    消息队列中的数据同样受到大小的约束,具体约束范围可通过msg_stat_queue的msg_qbytes看到.这段代码唯一有点小改动的地方就在接受消息时,指定了MSG_IPC_NOWAIT,不然如果目 ...

  5. 《linux程序设计》--读书笔记--第十四章信号量、共享内存和消息队列

    信号量:用于管理对资源的访问: 共享内存:用于在程序之间高效的共享数据: 消息队列:在程序之间传递数据的一种简单方法: 一.信号量 临界代码:需要确保只有一个进程或者一个执行线程可以进入这个临界代码并 ...

  6. IPC 共享内存和 消息队列(发送、接收、移除)以及键值的生成

    一.消息对列 消息队列,是消息的链接表,存放在内核中.一个消息队列由一个标识符(即队列ID)来标识. 特点: 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级.消息队列独立于发送与接收进 ...

  7. Linux 进程间通信:管道、共享内存、消息队列、信号量

    进程间通信 管道 共享内存 消息队列 信号量 进程间通信 https://blog.csdn.net/qq_35423154/article/details/105294963 在之前的一篇博客中讲过 ...

  8. linux 进程间通信及makefile 无名管道/有名管道/共享内存/信号/消息队列

    http://www.voidcn.com/article/p-hxvuiypm-mr.html https://www.cnblogs.com/wuyida/archive/2013/02/03/6 ...

  9. msgget();msgsnd();msgrcv();msgctl(); 消息队列 Linux进程间的通信方式之消息队列

    Linux进程间的通信方式 ----消息队列. 消息队列和共享内存类似 消息队列它允许一个或多个进程向它写消息,一个或多个进程向它写读消息. 消息队列存在于系统内核中,消息的数量受系统限制. 我们来看 ...

最新文章

  1. MPB:农科院田健、韩东飞等-​​水稻根系互作功能微生物的筛选方法
  2. 在SQL Server中分页结果的最佳方法是什么
  3. 金融贷款逾期的模型构建7——模型融合
  4. JEPaas代码_((表单)_输入字段值而改变值)
  5. springboot动态数据源切换(多数据源配置)
  6. OOD之问题空间到解空间—附FP的建模
  7. 用行为树的方式思考问题
  8. c语言变量强制转换,C语言——“=”中的强制类型转换
  9. 系统监控之SNMP协议理解
  10. 微信支付国庆消费数据出炉:门票交易增幅超8成 酒店行业交易增幅超7成
  11. 锂离子电池性能测试软件,锂离子电池的常规性能测试方式介绍
  12. 黑马程序员Java教程学习笔记(一)
  13. IIR滤波器的FPGA实现
  14. 第13届景驰-埃森哲杯广东工业大学ACM程序设计大赛(上)
  15. ISO 27002【实践指南】 -2022新标准
  16. python匹配邮箱_在Python中使用正则表达式同时匹配邮箱和电话并进行简单的分类...
  17. [ 笔记 ] 操作系统复习笔记
  18. Houdini粒子随机大小每帧变化问题
  19. pxtorem插件配置
  20. VS2022 .NET 特性重复

热门文章

  1. 【一 zedboard】PS端实现打印hello world
  2. 六“元”数智增长模型,企业元宇宙时代的经营新范式
  3. 戴尔服务器t420系统,戴尔PowerEdge T420
  4. 转载-simon:个人总结前端开发规范
  5. Android事件分发机制详解及解决文案:史上最全面、最易懂
  6. Java求解一元二次方程+单元测试(IDEA+Junit)
  7. Spring学习记录
  8. 强迫症的 Mac 设置指南
  9. 史上最全 | 华为敏捷管理经验全复盘
  10. 前端基础三:ECMAScript