原创:https://blog.csdn.net/ndzjx/article/details/89018951
进程间通信的机制,三种:
1:信号量(不同于线程POSIX信号量,这里是进程间)
2:共享内存
3:消息队列
信号量:二进制信号量/通用信号量
#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);
这些函数都是被设计用来对成组的信号量值来操作的。
key很像一个文件名,semget返回的与fopen返回的FILE*文件流很类似,不同进程可以用不同的信号量标识符指向同一个信号量。
semget: key不相关的进程可以通过它访问同一个信号量。其他信号量函数都使用semget返回的信号量标识。
num_sems,指定需要的信号量数目,几乎总是取1
sem_flags,一组标志,低端的9个比特是该信号量的权限,可以用IPC_CREATE 与权限按位或来取值。
semop: sembuf结构:
struct sembuf {
short sem_num; 信号量编号,除非使用一组信号量,否则,取值一般为0
short sem_op; 一次操作需要改变的数值,通常为1(P)或-1(V)
short sem_flg; 通常为SEM_UNDO,系统跟踪修改情况,进程没释放就终止时,系统自动释放它。
}
semctl: 如果有第四个参数,会是union semun结构:
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}
command 要采取的动作:通常是两个值:
SETVAL:把信号量初始化为一个已知的值。值在union semun中的val中。
IPC_RMID:删除无需继续使用的信号量标识符。
semun.h
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short int *array; /* array for GETALL, SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
#endif
sem1.c
/* After the #includes, the function prototypes and the global variable, we come to the
main function. There the semaphore is created with a call to semget, which returns the
semaphore ID. If the program is the first to be called (i.e. it's called with a parameter
and argc > 1), a call is made to set_semvalue to initialize the semaphore and op_char is
set to X. */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include "semun.h"
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
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);
if (argc > 1) {
if (!set_semvalue()) {
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
op_char = 'X';
sleep(2);
}
/* Then we have a loop which enters and leaves the critical section ten times.
There, we first make a call to semaphore_p which sets the semaphore to wait, as
this program is about to enter the critical section. */
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);
/* After the critical section, we call semaphore_v, setting the semaphore available,
before going through the for loop again after a random wait. After the loop, the call
to del_semvalue is made to clean up the code. */
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);
}
/* The function set_semvalue initializes the semaphore using the SETVAL command in a
semctl call. We need to do this before we can use the semaphore. */
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);
}
/* The del_semvalue function has almost the same form, except the call to semctl uses
the command IPC_RMID to remove the semaphore's ID. */
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");
}
/* semaphore_p changes the semaphore by -1 (waiting). */
static int semaphore_p(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1; /* P() */
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_p failed\n");
return(0);
}
return(1);
}
/* semaphore_v is similar except for setting the sem_op part of the sembuf structure to 1,
so that the semaphore becomes available. */
static int semaphore_v(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1; /* V() */
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_v failed\n");
return(0);
}
return(1);
}
$ ./sem1 1 &
$ ./sem1
共享内存:
并未提供同步机制。
#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);//将共享内存连接到地址空间中,SHM_RDONLY标志(使得连接的内存只读)
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);//将共享内存分离,并未删除它。
int shmget(key_t key, size_t size, int shmflg); //创建
shmctl中shmid_ds结构:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
command可以取三个值:
IPC_STAT/IPC_SET/IPC_RMID(删除共享内存段)
shm_com.h
/* A common header file to describe the shared memory we wish to pass about. */
#define TEXT_SZ 2048
struct shared_use_st {
int written_by_you;
char some_text[TEXT_SZ];
};
shm1.c
/* Our first program is a consumer. After the headers the shared memory segment
(the size of our shared memory structure) is created with a call to shmget,
with the IPC_CREAT bit specified. */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
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);
}
/* We now make the shared memory accessible to the program. */
shared_memory = shmat(shmid, (void *)0, 0);
if (shared_memory == (void *)-1) {
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n", (int)shared_memory);
/* The next portion of the program assigns the shared_memory segment to shared_stuff,
which then prints out any text in written_by_you. The loop continues until end is found
in written_by_you. The call to sleep forces the consumer to sit in its critical section,
which makes the producer wait. */
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_text);
sleep( rand() % 4 ); /* make the other process wait for us ! */
shared_stuff->written_by_you = 0;
if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
running = 0;
}
}
}
/* Lastly, the shared memory is detached and then deleted. */
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);
}
shm2.c
/* The second program is the producer and allows us to enter data for consumers.
It's very similar to shm1.c and looks like this. */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
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 attached at %X\n", (int)shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
while(running) {
while(shared_stuff->written_by_you == 1) {
sleep(1);
printf("waiting for client...\n");
}
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared_stuff->some_text, 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);
}
消息队列:
优势:
1:独立于发送和接收进程而存在。
2:可以提前查看紧急消息
但,与管道一样,每个数据块都有一个最大长度限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。
MSGMAX,消息的最大长度
MSGMNB,队列的最大长度
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)
int msgget(key_t key, int msgflg) //创建
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
消息的结构受两方面约束:
1:长度必须小于系统规定的上限
2:必须以一个长整型成员变量开始
stuct my_message{
long int message_type;
/* The date you wish to transfer */
}
msgrcv/msgsnd中msg_sz是msg_ptr指向的消息的长度,这个长度不能包括长整型消息类型成员变量的长度。
msgflg控制消息队列满或队列消息达到系统的限制时要发生的事情,IPC_NOWAIT进程挂起以等待队列腾出可用空间。
msgrcv中msgtype代表接收优先级,为0,获取第一个可用消息。大于0,获取具有相同消息类型的第一个消息。小于0,获取消息类型<=msgtype的绝对值的第一个消息。
例如,如果想接收类型<=n的消息,就把msgtype设置为-n
成功时,返回放到接收缓存区中的字节数。
msg1.c
/* Here's the receiver program. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct my_msg_st {
long int my_msg_type;
char some_text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid;
struct my_msg_st some_data;
long int msg_to_receive = 0;
/* First, we set up the message queue. */
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1) {
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
/* Then the messages are retrieved from the queue, until an end message is encountered.
Lastly, the message queue is deleted. */
while(running) {
if (msgrcv(msgid, (void *)&some_data, BUFSIZ,
msg_to_receive, 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);
}
msg2.c
/* The sender program is very similar to msg1.c. In the main set up, delete the
msg_to_receive declaration and replace it with buffer[BUFSIZ], remove the message
queue delete and make the following changes to the running loop.
We now have a call to msgsnd to send the entered text to the queue. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st {
long int my_msg_type;
char some_text[MAX_TEXT];
};
int main()
{
int running = 1;
struct my_msg_st some_data;
int msgid;
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);
}
while(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);
}
假设消息队列中有空间,可以先发送,然后在接收者启动之前就退出:
./msg2
…
./msg1
IPC机制一个让人烦恼的问题:编写错误的程序,或因为某些原因而执行失败的程序将把它的IPC资源(如消息队列中的数据)遗留在系统中。
一些命令可以查看和删除IPC
./ipcs -s 查看信号量
./ipcs -m 查看共享内存
./ipcs -q 茶康消息队列
./ipcrm -s 768 或 ./ipcrm sem 768 删除指定信号量
./ipcrm -m <id> 删除共享内存
./ipcrm -q <id> 删除消息队列