相较于线程间通信,线程间通信由于是在同一进程内的,对于某些全局的静态变量进行读写操作,无疑方便的多。而不同进程之间的通信,由于在内存中位置不同,因此通信起来较为复杂,同时进程间通信有时还会考虑进程之间互斥访问的问题,及在多个进程同时就使用某一资源通信时存在的竞争。
进程通信的方式,我目前了解到的有:管道(有名和匿名)、消息队列、共享内存,在此基础上会介绍操作系统提供的信号量(实现进程通信时互斥访问)
1、管道:管道分为有名管道和匿名管道。其中匿名管道是存在与内存中的,只能用于具有亲缘关系的进程之间进行通信,及通过fork创建出来的父子进程或兄弟进程。而有名管道则是通过使用路径名进行创建的(类似于文件),存在于文件系统之中,可以实现两个互不相关的进程之间的通信。管道传输的是无格式的字节流,且缓冲区的大小受到限制(使用ulimit -a可以查看);数据只能有一个进程流向另一个进程,如果要实现双端通信,则需要建立两个管道。
下面是一个有名管道的例子:
//read.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#define P_FIFO "/home/sakura/Desktop/p_fifo"
int main(int argc, char ** argv)
{
char buf[100];
int fd;
memset(buf, 0, sizeof(buf)); //initialize memory
if(0 == access(P_FIFO, F_OK)){ //if pipe existed, delete it
execlp("rm", "-f", P_FIFO, NULL);
printf("access now!\n");
}
if(mkfifo(P_FIFO, 0777) < 0){
printf("create pipe failed\n");
}
fd = open(P_FIFO, O_RDONLY|O_NONBLOCK); //open the pipe
while(1){ //read
memset(buf, 0, sizeof(buf));
if(read(fd, buf, 100) == 0){
printf("No data!\n");
}
else{
printf("get data: %s\n", buf);
}
sleep(1);
}
return 0;
}
//write.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#define P_FIFO "/home/sakura/Desktop/p_fifo"
int main(int argc, char ** argv)
{
int fd;
if(argc < 2)
printf("No input");
fd = open(P_FIFO, O_WRONLY|O_NONBLOCK);
write(fd, argv[1], 100);
close(fd);
return 0;
}
介绍几个函数:
int pipe(int fildes[2]); //创建匿名管道
int mkfifo(const char *path, mode_t mode); //创建匿名管道
详细参加:点击打开链接
2、消息队列:消息队列用于同一台机器上的进程间通信,是一个在系统内核中保存消息的队列,在系统内核中以消息链表的形式出现。
相关函数:
int msgget(key_t key, int msgflg); //创建消息队列
通过一个指定的key值创建一个消息队列,其余进程可以通过同样的key值获取这个消息队列的msqid
,用以下的函数进行通信;msgflg表示的是消息队列的访问权限ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); //读消息
一般而言,使用消息队列会使用以下的结构体
struct msg_st{
long int msgType; //指定消息类型
char msgText; //消息内容
}
主要是第二个参数,这里传入的是一个空指针,因此传入结构时需要进行转换(void *)&msg_st,后两个参数msgtyp会根据指定值获取消息队列中的对应的消息,具体解释参见上面链接中的内容。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); //写消息
int msgctl(int msqid, int cmd, struct msqid_ds *buf); //设置消息队列属性
参数cmd,提供了几个指定的命令:
IPC_STAT:
将与msqid关联的msqid_ds数据结构的每个成员的当前值放入由buf指向的结构中。这个结构的内容在<sys / msg.h>中定义。
IPC_SET:
将与msqid关联的msqid_ds数据结构的以下成员的值设置为由buf指向的结构中找到的对应值:
msg_perm.uid
msg_perm.gid
msg_perm.mode
msg_qbytes
另外,msg_ctime时间戳应设置为当前时间,如IPC常规说明中所述。
IPC_SET:只能由具有适当特权的进程执行,或者其有效用户ID等于与msqid关联的msqid_ds数据结构中的msg_perm.cuid或msg_perm.uid的值。只有具有适当权限的进程才能提高msg_qbytes的值。
IPC_RMID:
从系统中删除由msqid指定的消息队列标识符,并销毁与之关联的消息队列和msqid_ds数据结构。 IPC_RMD只能由具有适当权限的进程执行,或者只有与msqid关联的msqid_ds数据结构中的有效用户ID等于msg_perm.cuid或msg_perm.uid的值。
主要是最后一个IPC_RMID用于删除
代码示例:
//receive
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
struct msg_st{
long msgType;
char msgText[BUFSIZ];
};
int main()
{
int msg_id = -1;
struct msg_st msg;
int flag = 1;
long int msg_type = 0;
//establish the message queue
msg_id = msgget((key_t)1234, 0666 | IPC_CREAT); //0666 the file permission
if(-1 == msg_id){
printf("create message queue error!\n");
return -1;
}
//get the message from the queue
while(flag){
if(msgrcv(msg_id, (void*)&msg, BUFSIZ, msg_type, 0) == -1){
fprintf(stderr, "can't receive the message!\n");
exit(EXIT_FAILURE);
}
printf("Receive message: %s", msg.msgText);
if(0 == strncmp(msg.msgText, "end", 3)){
flag = 0;
}
}
//delete the message queue
if(msgctl(msg_id, IPC_RMID, 0) == -1){
fprintf(stderr, "msgctl(IPC_RMID) failed!\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
//send
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
struct msg_st{
long msgType;
char msgText[BUFSIZ];
};
int main()
{
int msg_id = -1;
struct msg_st msg;
int flag = 1;
long int msg_type = 0;
char buf[BUFSIZ];
//establish the message queue
msg_id = msgget((key_t)1234, 0666 | IPC_CREAT); //0666 the file permission
if(-1 == msg_id){
printf("create message queue error! maybe get the id error\n");
return -1;
}
//get the message from the queue
while(flag){
printf("Enter msg:");
fgets(buf, BUFSIZ, stdin);
strcpy(msg.msgText, buf);
msg.msgType = 1;
if(msgsnd(msg_id, (void *)&msg, BUFSIZ, 0) == -1){
fprintf(stderr, "can't sent the message!\n");
exit(EXIT_FAILURE);
}
if(0 == strncmp(msg.msgText, "end", 3)){
flag = 0;
}
sleep(1);
}
//delete the message queue
exit(EXIT_SUCCESS);
}
3、共享内存:共享内存是不同的进程将同一段物理内存映射到自己的内存空间,也就是说多个进程在物理上共用了同一块内存,若其中有一个程序对共享内存进行了修改,那么其他进程通过访问即可获得修改后的值,获得传输的数据。需要注意的是,需要对共享内存的结构有一个统一的解释。
先上代码:原型是生产者和消费者
//consumer
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include"shm_com.h"
#define SHM_KEY 1234
#define SEM_KEY 8086
union semun{
int val;
};
int main()
{
int shmid, semid;
srand((unsigned)getpid());
shmid = shmget(SHM_KEY, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if(-1 == shmid){
fprintf(stderr, "shmget failed!");
exit(EXIT_FAILURE);
}
void *shared_memory = (void *)0;
shared_memory = shmat(shmid, (void *)0, 0);
if(shared_memory == (void *)0 - 1){
fprintf(stderr, "shmat failed!");
exit(EXIT_FAILURE);
}
printf("Memory attached at: %X\n", (long)shared_memory);
struct shared_use_st *shared_stuff;
shared_stuff = (struct shared_use_st *)shared_memory;
shared_stuff->written = 0;
semid = semget(SEM_KEY, 2, IPC_CREAT | 0666);
union semun semun_t;
semun_t.val = 0;
semctl(semid, 0, SETVAL, semun_t);
semun_t.val = 1;
semctl(semid, 1, SETVAL, semun_t);
struct sembuf sembuf_t;
int running = 1;
while(running){
sembuf_t.sem_num = 0;
sembuf_t.sem_op = -1;
sembuf_t.sem_flg = SEM_UNDO;
semop(semid, &sembuf_t, 1);
if(shared_stuff->written){
printf("Read from shared memory: %s", shared_stuff->text);
sleep(rand() % 4);
shared_stuff->written = 0;
if(0 == strncmp(shared_stuff->text, "end", 3)){
running = 0;
}
}
sembuf_t.sem_num = 1;
sembuf_t.sem_op = 1;
sembuf_t.sem_flg = SEM_UNDO;
semop(semid, &sembuf_t, 1);
}
if(-1 == shmdt(shared_memory)){
fprintf(stderr, "shmdt failed!\n");
exit(EXIT_FAILURE);
}
if(-1 == shmctl(shmid, IPC_RMID, 0)){
fprintf(stderr, "shmctl(IPC_RMID) failed!\n");
exit(EXIT_FAILURE);
}
return 0;
}
//producer
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include"shm_com.h"
#define SHM_KEY 1234
#define SEM_KEY 8086
union semun{
int val;
};
int main()
{
int shmid, semid;
srand((unsigned)getpid());
shmid = shmget(SHM_KEY, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if(-1 == shmid){
fprintf(stderr, "shmget failed!");
exit(EXIT_FAILURE);
}
void *shared_memory = (void *)0;
shared_memory = shmat(shmid, (void *)0, 0);
if(shared_memory == (void *)0 - 1){
fprintf(stderr, "shmat failed!");
exit(EXIT_FAILURE);
}
printf("Memory attached at: %X\n", (long)shared_memory);
struct shared_use_st *shared_stuff;
shared_stuff = (struct shared_use_st *)shared_memory;
semid = semget(SEM_KEY, 2, 0666);
struct sembuf sembuf_t;
int running = 1;
char buffer[BUFSIZ];
while(running){
sembuf_t.sem_num = 1;
sembuf_t.sem_op = -1;
sembuf_t.sem_flg = SEM_UNDO;
semop(semid, &sembuf_t, 1);
while(1 == shared_stuff->written){
sleep(1);
printf("Waiting for consuming...\n");
}
printf("Enter the text:");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared_stuff->text, buffer, TEXT_SZ);
shared_stuff->written = 1;
if(0 == strncmp(shared_stuff->text, "end", 3)){
running = 0;
}
sembuf_t.sem_num = 0;
sembuf_t.sem_op = 1;
sembuf_t.sem_flg = SEM_UNDO;
semop(semid, &sembuf_t, 1);
}
if(-1 == shmdt(shared_memory)){
fprintf(stderr, "shmdt failed!\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
//shared memory
#ifndef _SHMCOM_H_HEADER_
#define _SHMCOM_H_HEADER_
#define TEXT_SZ 2048
struct shared_use_st
{
/* data */
int written;
char text[TEXT_SZ];
};
#endif
(1)共享内存的创建:
SHM_KEY是key_t类型
shmget函数通过一个SHM_KEY,创建一个共享内存,第二个参数是大小,第三个参数是标志位即如何使用这块空间。如果对应的共享内存已存在,即返回该块的shmid
shmat函数通过shmid将共享内存纳入自己的进程空间,addr以及flag参数决定以什么样的方式来将进程纳入自己的空间,函数返回值即是共享内存的地址(得到地址后,通过适当的类型转换即可使用)
shmdt函数用于将共享内存从进程中剥离,成功返回0,失败返回-1
shmctl函数完成对共享内存的控制,其中cmd是控制指令,IPC_STAT(获得共享内存状态,复制到buf中),IPC_SET(改变共享内存状态,将buf指向的值复制到shmid代表的共享内存中),IPC_RMID(删除这片共享内存)
通过这几个函数,可以实现不同进程之间对于共享内存的操作,对于共享内存的读写,按照普通方式操作即可(前提是将共享内存转换成正确的结构类型)
代码中的几处sem使用了SYSTEM V信号量进行对内存的互斥访问,有一篇很详细的博客讲解信号量,附上链接,侵删