0. 前言
进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间。但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据的传递、同步和异步的机制。
当然,这些机制不能由哪一个进程进行直接管理,只能由操作系统来完成其管理和维护,Linux提供了大量的进程间通信机制,包括同一个主机下的不同进程和网络主机间的进程通信,如下图所示:
同主机间的信息交互:
- 无名管道:
特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
问题:多进程用同一管道通信容易造成交叉读写的问题- 有名管道:
FIFO(First In First Out),方向为单向(双向需两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞- 消息队列:
可用于同主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递- 共享内存:
可实现同主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争- 同主机进程间同步机制:信号量(Semaphore)
- 同主机进程间异步机制:信号(Signal)
网络主机间数据交互:Socket(套接字)
1. IPC
IPC, Inter-Process Communication,进程间通信,包括消息队列、信号量和共享内存三种机制。
IPC使用前必须要先创建,每种IPC都有其创建者、所有者和访问权限。
使用ipcs
可以查看系统中的IPC工具:
[niesh@niesh ~]$ ipcs--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 131072 niesh 600 524288 2 目标
0x00000000 163841 niesh 600 4194304 2 目标--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
- key:
用于创建ID值(ID值由一个进程创建的话,由于进程资源的私有性,另一个进程无法获取到该ID);采用统一key值创建的ID是相同的;- id:
IPC机制的唯一标识
1). 获取key值 - ftok():
- 作用:
获取key值头文件:
#include <sys/ipc.h>
函数原型:
key_t ftok(const char *pathname, int proj_id)
参数:
pathname:文件名
proj_id: 作为key值的组成部分,用到了低8位返回值:
成功:key值
bit 描述 31-24 proj_id & 0xFF (低8位) 23-16 stat(pathname).st_dev & 0xFF (低8位) 15-0 stat(pathname).st_ino & 0xFFFF (低16位) 失败:-1
几个结构体需要详细了解:
- struct msqid_ds: 消息队列数据结构
- struct msg: 单个消息的数据结构
- struct msgbuf: 用户自定义消息缓冲区
- struct msginfo:
2. 消息队列
1). 常用数据结构
①. struct msqid_ds
/* FILE: /usr/include/linux/msg.h *//* Obsolete, used only for backwards compatibility and libc5 compiles */
struct msqid_ds {struct ipc_perm msg_perm;struct msg *msg_first; /* first message on queue,unused */struct msg *msg_last; /* last message in queue,unused */__kernel_time_t msg_stime; /* last msgsnd time */__kernel_time_t msg_rtime; /* last msgrcv time */__kernel_time_t msg_ctime; /* last change time */unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */unsigned long msg_lqbytes; /* ditto */unsigned short msg_cbytes; /* current number of bytes on queue */unsigned short msg_qnum; /* number of messages in queue */unsigned short msg_qbytes; /* max number of bytes on queue */__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
/* FILE: /usr/include/bits/ipc.h *//* Data structure used to pass permission information to IPC operations. */
struct ipc_perm{__key_t __key; /* Key. */__uid_t uid; /* Owner's user ID. */__gid_t gid; /* Owner's group ID. */__uid_t cuid; /* Creator's user ID. */__gid_t cgid; /* Creator's group ID. */unsigned short int mode; /* Read/write permission. */unsigned short int __pad1;unsigned short int __seq; /* Sequence number. */unsigned short int __pad2;__syscall_ulong_t __unused1;__syscall_ulong_t __unused2;};
②. struct msg
/* FILE: /usr/src/kernels/3.10.0-327.el7.x86_64/include/linux/msg.h *//* one msg_msg structure for each message */
struct msg_msg {struct list_head m_list;long m_type;size_t m_ts; /* message text size */struct msg_msgseg* next;void *security;/* the actual message follows immediately */
};
③. struct msgbuf (编程时,必须自己实现,因为mtext大小未定义)
/*FILE: /usr/include/linux/msg.h *//* message buffer for msgsnd and msgrcv calls */
struct msgbuf {long mtype; /* type of message */char mtext[1]; /* 信息实体(用户可自定义大小) */
};
④. struct msginfo
/* FILE: /usr/include/linux/msg.h *//* buffer for msgctl calls IPC_INFO, MSG_INFO */
struct msginfo
{int msgpool; /* Size in kibibytes of buffer poolused to hold message data;unused within kernel */int msgmap; /* Maximum number of entries in messagemap; unused within kernel */int msgmax; /* Maximum number of bytes that can bewritten in a single message */int msgmnb; /* Maximum number of bytes that can bewritten to queue; used to initializemsg_qbytes during queue creation (msgget(2)) */int msgmni; /* Maximum number of message queues */int msgssz; /* Message segment size;unused within kernel */int msgtql; /* Maximum number of messages on all queuesin system; unused within kernel */unsigned short int msgseg; /* Maximum number of segments;unused within kernel */
};
2). 消息队列的操作
①. 创建消息队列 - msgget():
- 作用:
创建消息队列头文件:
#include <sys/msg.h>
函数原型:
int msgget(key_t key, int msgflg)
参数:
key: 有函数 ftok 返回的key值
msgflg: 消息队列的访问权限
msgflg num description IPC_CREAT 0x1000 若key不存在,则创建;存在,则返回ID IPC_EXCL 0x2000 若key存在,返回失败 IPC_NOWAIT 0x4000 若需要等待,直接返回错误 返回值:
成功:消息队列的ID
失败:-1
②. 消息队列属性控制 - msgctl():
- 作用:
设置/获取消息队列的属性值头文件:
#include <sys/msg.h>
函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
参数:
msqid: 消息队列ID
cmd: 要执行的操作
micro number description IPC_RMID 0 删除消息队列 IPC_SET 1 设置ipc_perm的参数 IPC_STAT 2 从内核结构体msqid复制信息到msgqid_ds IPC_INFO 3 获取限制信息到msginfo结构体 MSG_INFO 12 同IPC_INFO,但会返回msginfo.msgpool/msgmap/msgtql 返回值:
成功: 0(IPC_RMID/IPC_SET/IPC_STAT), 消息队列数组索引的最大值(IPC_INFO/MSG_INFO)
失败:-1
③. 发送/接收消息队列 - msgsnd()/msgrcv():
- 作用:
发送消息到消息队列(添加到尾端)/接收消息头文件:
#include <sys/msg.h>
函数原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
.
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
参数:
msqid: 消息队列的ID值,msgget()的返回值
msgp: struct msgbuf,(发送/接收数据的暂存区,由用户自定义大小)
msgsz: 发送/接收消息的大小(范围:0~MSGMAP)
msgflg: 当达到系统为消息队列所指定的界限时,采取的操作(一般设置为0)
msgtyp:
msgtyp description = 0 读取队列中的第一个消息 > 0 读取消息队列的第一条 msgbuf.mtype=msgtype
的消息< 0 读取第一条 lowest msgbuf.mtype < abs(msgtyp)
的消息返回值:
成功: 0 (for msgsnd()); 实际写入到mtext的字符个数 (for msgrcv())
失败:-1
3. 示例代码
本程序主要是实现两个进程通过消息队列发送信息:
- server:
- 等待接收客户端发送的数据,若时间超出600s,则自动exit;
- 当收到信息后,打印接收到的数据;并原样的发送给客户端,由客户端显示
- client:
- 启动两个进程(父子进程),父进程用于发送数据,子进程接收由server发送的数据;
- 发送数据:由使用者手动输入信息,回车后发送;当写入“end~”后,退出本进程
- 接收数据:接收由Server端发送的数据信息,并打印
代码如下:
1. Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>#define BUF_SIZE 128//Rebuild the strcut (must be)
struct msgbuf
{long mtype;char mtext[BUF_SIZE];
};int main(int argc, char *argv[])
{//1. creat a mseg queuekey_t key;int msgId;printf("THe process(%s),pid=%d started~\n", argv[0], getpid());key = ftok(".", 0xFF);msgId = msgget(key, IPC_CREAT|0644);if(-1 == msgId){perror("msgget");exit(EXIT_FAILURE);}//2. creat a sub process, wait the server messagepid_t pid;if(-1 == (pid = fork())){perror("vfork");exit(EXIT_FAILURE);}//In child processif(0 == pid){while(1){alarm(0);alarm(100); //if doesn't receive messge in 100s, timeout & exitstruct msgbuf rcvBuf;memset(&rcvBuf, '\0', sizeof(struct msgbuf));msgrcv(msgId, &rcvBuf, BUF_SIZE, 2, 0); printf("Server said: %s\n", rcvBuf.mtext);}exit(EXIT_SUCCESS);}else //parent process{while(1){usleep(100);struct msgbuf sndBuf;memset(&sndBuf, '\0', sizeof(sndBuf));char buf[BUF_SIZE] ;memset(buf, '\0', sizeof(buf));printf("\nInput snd mesg: ");scanf("%s", buf);strncpy(sndBuf.mtext, buf, strlen(buf)+1);sndBuf.mtype = 1;if(-1 == msgsnd(msgId, &sndBuf, strlen(buf)+1, 0)){perror("msgsnd");exit(EXIT_FAILURE);}//if scanf "end~", exitif(!strcmp("end~", buf))break;}printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());}return 0;
}
2. Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>#define BUF_SIZE 128//Rebuild the strcut (must be)
struct msgbuf
{long mtype;char mtext[BUF_SIZE];
};int main(int argc, char *argv[])
{//1. creat a mseg queuekey_t key;int msgId;key = ftok(".", 0xFF);msgId = msgget(key, IPC_CREAT|0644);if(-1 == msgId){perror("msgget");exit(EXIT_FAILURE);}printf("Process (%s) is started, pid=%d\n", argv[0], getpid());while(1){alarm(0);alarm(600); //if doesn't receive messge in 600s, timeout & exitstruct msgbuf rcvBuf;memset(&rcvBuf, '\0', sizeof(struct msgbuf));msgrcv(msgId, &rcvBuf, BUF_SIZE, 1, 0); printf("Receive msg: %s\n", rcvBuf.mtext);struct msgbuf sndBuf;memset(&sndBuf, '\0', sizeof(sndBuf));strncpy((sndBuf.mtext), (rcvBuf.mtext), strlen(rcvBuf.mtext)+1);sndBuf.mtype = 2;if(-1 == msgsnd(msgId, &sndBuf, strlen(rcvBuf.mtext)+1, 0)){perror("msgsnd");exit(EXIT_FAILURE);}//if scanf "end~", exitif(!strcmp("end~", rcvBuf.mtext))break;}printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());return 0;
}