APUE
一、进程间通信
什么是进程间通信(ipc)
进程间通信
- 数据传输(多个进程间进行数据传输)
- 资源共享(不同进程间使用系统资源不同,eg:文件,内存)
- 事件通知(当进程发生特定事件,希望把这些事件告诉给另外一些进程,让另外一些进程及时作出反应)
- 进程控制(在一个进程里控制另一个进程的执行。eg:用调试工具进行程序调试,需要控制程序单步执行,在调试工具进程里对另一个进程进行控制)
Linux系统下的ipc
- 早期unix系统ipc(Linux系统诞生之初已经存在的两个系统间通信)
- 管道(数据传输)
- 信号(事件通知)
- fifo(数据传输)
- system-v ipc(贝尔实验室在早期Linux系统优化来的)
- system-v 消息队列(数据传输,进程控制)
- system-v 信号量(资源共享,事件通知)
- system-v 共享内存(数据传输,效率高)
- socket ipc(BSD)(允许不同机器间进行系统之间通信)
- posix ipc(IEEE)
- posix 消息队列
- posix 信号量
- posix 共享内存
1.1 管道
单工:类似队列(先进先出)
注意: 管道天生实现阻塞,管道必须要凑齐读写双方才能建立
匿名管道:piepe(),磁盘文件系统上看不到文件(ls不显示),相当于直接给了我们一个文件描述符或者FILE* 。
注意: 如果两个进程之间没有血缘关系,是不能用匿名管道进行通信的,因为另一个进程找不到文件位置。
命名管道:mkfifo(), 从磁盘文件系统上看到的(ls文件类型为P 的文件),本质上就是当前磁盘上存在的一个P类型的文件,没有血缘关系的进程之间 可以用命名管道进行通信
1.1.1 函数pipe()
描述:匿名管道的创建
手册:man pipe
头文件:
#include <unistd.h>
函数原型:
int pipe(int pipefd[2]);
- pipe()返回两个文件描述符填充到 pipefd[]数组中( pipefd[0]为读端,pipefd[1]为写端 )
返回值:
成功:0
失败:-1,errno
特点:
- 特殊文件(没有名字),无法使用open,但是可以使用close。
- 只能通过子进程继承文件描述符的形式来使用
- write和read操作可能会阻塞进程
- 所有文件描述符被关闭之后,无名管道被销毁
使用步骤:
- 父进程pipe无名管道
- fork子进程
- close无用端口
- write/read读写端口
- close读写端口
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> //查看错误代的erron,调试程序的一个重要方法
int main(int argc,char *argv[])
{
pid_t pid;
const char date[]="pipe test program";
char buf[256];
/*定义管道描述符数组,定义了2个管道,一般pipe_fd[0]是接收管道描述符,pipe_fd[1]是写管道描述符*/
int pipe_fd[2];
int status;
int real_read,real_write;
/*向buf内的256byte位填充“0”*/
memset((void*)buf,0,sizeof(buf));
if(pipe(pipe_fd) < 0){
printf("pipe creat error!\n");
exit(1);
}
if((pid=fork()) == 0){
close(pipe_fd[1]);
if((real_read=read(pipe_fd[0],buf,256))>0){
printf("%d bytes read from the pipe is : %s\n",real_read,buf);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid > 0){
close(pipe_fd[0]);
if((real_write=write(pipe_fd[1],date,strlen(date)))!= -1){
printf("parent write %d bytes : %s\n",real_write,date);
}
close(pipe_fd[1]);
wait(&status);
exit(0);
}
}
代码演示:
功能:父子进程 父写子读
1.1.2 函数mkfifo()
描述:命名管道的创建
手册:man 3 mkfifo
头文件:
#include <sys/types.h>
#include <sys/stat.h>
函数原型:
int mkfifo(const char *pathname, mode_t mode);
- pathname – 指定我们所创建的命名管道的名字
- mode – 指定操作权限
返回值:
成功:0
失败:-1,errno
特点:
- 有文件名,可以使用open函数打开
- 任意进程间数据传输
- write和read操作可能会阻塞进程
- write具有"原子性"
使用步骤:
- 第一个进程mkfifo有名管道
- open有名管道,write/read数据
- close有名管道
- 第二个进程open有名管道,read/write数据
- close有名管道
代码演示:
fifo_read
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MYFIFO "/home/ldy/野火嵌入式学习/SYS_Program/namefifo"
int main(int argc,char *argv[])
{
char buff[256];
int fd;
int real_read;
/* access函数中的F_OK参数用来检测当前管道是否存在 */
/* errno函数中的EEXIST参数用来检测文件是否存在 */
if(access(MYFIFO,F_OK)==-1){
if((mkfifo(MYFIFO,0666)<0) && (errno != EEXIST)){
printf("creat fifo error!\n");
exit(1);
}
}
fd = open(MYFIFO,O_RDONLY);
if(fd==-1){
printf("open error!\n");
exit(1);
}
while(1){
memset(buff,0,sizeof(buff));
if((real_read=read(fd,buff,256))>0){
printf("read %s from fifo\n",buff);
}
}
close(fd);
exit(0);
}
fifo_write
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MYFIFO "/home/ldy/野火嵌入式学习/SYS_Program/namefifo"
int main(int argc,char *argv[])
{
int fd;
char buff[256];
int real_write;
if(argc<=1){
printf("usage:./fifo_write string\n");
exit(1);
}
sscanf(argv[1],"%s",buff);
fd=open(MYFIFO,O_WRONLY);
if(fd==-1){
printf("open error!\n");
exit(1);
}
if((real_write=write(fd,buff,256))>0){
printf("write '%s' to fifo\n",buff);
}
close(fd);
exit(0);
}
1.2 XSI IPC
命令:
ipcs – 查看当前IPC(msg,sem,shm状态)
ipcrm – 删除指定IPC
msg(消息队列) sem(信号量) shm(共享内存)
xxxget:创建
xxxop:操作控制
xxxctl:打杂的(初始化,销毁)
主动端:先发包的一方(后运行起来)
被动端:先收包的一方(先运行起来)
区分主动被动:不管后边谁收谁发,只看第一个谁先发,谁是主动端
1.2.1 标识符和键
有亲缘
无亲缘关系进程通信 – 协议:双方约定对话格式
C/S – Client/Server
1.2.1.1 ftok()
描述:利用给定的路径名(inode号)和一个项目ID 产生一个键(key值)
产生IPC Key值,将路径名和项目标识符转换为System V IPC密钥
手册:man ftok
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
函数原型:
key_t ftok(const char *pathname, int proj_id);
- pathname – 一个现有的文件,产生键时,只使用id参数的低八位。
- proj_id – 项目ID 一个大于0的整型
返回值:
成功:该消息队列的Key值
失败:-1,并且设置errno
1.3 msg消息队列
1.3.1 msgget()
描述:用来创建新的消息队列或获取已有的消息队列
手册:man msgget
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:
int msgget(key_t key, int msgflg);
- key – 消息队列key值,用来产生唯一的msgid的
- msgflg – 创建当前消息队列的特殊要求
IPC_CREAT:如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建,否则产生一个错误并返回。
返回值:
成功:消息队列的msgid
失败:-1,errno
1.3.2 msgsnd() & msgrcv()
描述:发送消息队列 & 接收消息队列
手册:man msgop
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#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);
//结构体定义注意:
struct msgbuf {
long mtype; /* message type, must be > 0 结构体开始必须要包含此成员*/
char mtext[1]; /* message data */
};
-
msqid – 消息队列ID
-
msgp – 待发送/接收数据首地址
-
msgsz – 待发送/接收数据信息的大小,而不是全部的数据信息大小:sizeof(buf)-sizeof(long)
-
msgtyp – 是否挑选消息来接收,比如接收第几个包
可以实现多个不同需求的进程通过同一个msg消息队列通信
-
msgflg – 特殊要求
返回值:
成功:msgsnd返回0,msgrcv返回
失败:-1,errno
1.3.3 msgctl()
描述:控制一个消息队列,如销毁,设置等等
手册:man msgctl
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
msqid – 消息队列ID
-
cmd – 对消息队列msqid 执行 cmd命令
IPC_STAT IPC_SET IPC_RMID 删除当前消息队列 IPC_INFO (Linux-specific) MSG_INFO (Linux-specific) MSG_STAT (Linux-specific)
-
buf – 传参
返回值:
成功: cmd为IPC_STAT, IPC_SET, IPC_RMID 返回 0
失败:-1,errno
1.3.4 代码演示
过程:
- 拟定协议(.h文件)
- 获取键值(ftok)
- 发送端:获取msgid,接收端:获取msgid并创建消息队列 (msgget)
- 发送端:发送消息,接收端:接收消息 (msgsnd/msgrcv)
- 接收端销毁消息队列(msgctl)
prtot.h
#ifndef PROTO_H__
#define PROTO_H__
#define KEYPATH "/etc/services"
#define KEYPROJ 'A' // 12 "12"
#define NAMESIZE 32
struct msg_st
{
long mtype;
char name[NAMESIZE];
int math;
int chinese;
};
#endif
snder.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include "proto.h"
int main()
{
key_t key;
int msgid;
struct msg_st sbuf;
key = ftok(KEYPATH,KEYPROJ);
if(key < 0)
{
perror("fork()");
exit(1);
}
msgid = msgget(key,0);
if(msgid < 0)
{
perror("msgget()");
exit(1);
}
sbuf.mtype = 1;
strcpy(sbuf.name,"Alan");
sbuf.math = rand()%100;
sbuf.chinese = rand()%100;
if(msgsnd(msgid,&sbuf, sizeof(sbuf)-sizeof(long),0) < 0)
{
perror("msgsnd()");
exit(1);
}
puts("OK");
exit(0);
}
rcver.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "proto.h"
int main()
{
key_t key;
int msgid;
struct msg_st rbuf;
key = ftok(KEYPATH,KEYPROJ);
if(key < 0)
{
perror("fork()");
exit(1);
}
msgid = msgget(key, IPC_CREAT|0600);
if(msgid < 0)
{
perror("msgget()");
exit(1);
}
while(1)
{
if(msgrcv(msgid, &rbuf, sizeof(rbuf)-sizeof(long),0,0) < 0)
{
perror("msgrcv()");
exit(1);
}
printf("NAME:%s\n",rbuf.name);
printf("MATH:%d\n",rbuf.math);
printf("CHINESE:%d\n",rbuf.chinese);
}
msgctl(msgid,IPC_RMID,NULL);
exit(0);
}
1.4 shm共享内存
1.4.1 shmget()
描述: 分配一个System V共享内存段
手册:man shmget
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型:
int shmget(key_t key, size_t size, int shmflg);
-
key –
如果没有亲缘关系的进程中 创建和获取 和之前 消息队列是一样的
如果在有亲缘关系的进程中,fork()之后,每一个子进程都可以拿到父进程创建的 key值,此时不再关心key值,此时可以设置为 IPC_PRIVATE,表示该IPC 为 匿名IPC 不需要ftok
-
size – 要设置的共享内存的大小
-
shmflg – 特殊要求
返回值:
成功:返回有效的共享内存标识符
失败:-1,errno
1.4.2 shmat() & shmdt()
描述: 把共享内存映射过来 & 把共享内存进行解除映射
手册:man shmop
头文件:
#include <sys/types.h>
#include <sys/shm.h>
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
-
shmid – 共享内存ID
-
shmaddr – 需要映射到我当前空间的具体位置(NULL:表示函数帮忙在当前进程空间中寻找一块可用的内存地址)
-
shmflg – 特殊要求(0:没有特殊要求)
返回值:
成功:shmat返回共享内存的首地址,shmdt返回0
失败:shmat返回(void *) -1设置errno,shmdt返回-1,设置errno
1.4.3 shmctl()
描述: System V共享内存控制 如销毁
手册:man shmctl
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-
shmid – 共享内存ID
-
cmd – 命令操作 (如:IPC_RMID 销毁 )
-
buf – 是否需要传参,需要传递的参数
返回值:
成功:0
失败:-1,errno
1.4.4 代码演示
功能:父子进程进 使用 共享内存 进行通信
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define MEMSIZE 1024
int main(int argc,char *argv[])
{
char *str;
pid_t pid;
int shmid;
shmid = shmget(IPC_PRIVATE, MEMSIZE,0600);
if(shmid < 0)
{
perror("shmget()");
exit(1);
}
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0) // child write
{
str = shmat(shmid,NULL,0);
if(str == (void *) -1)
{
perror("shmat()");
exit(1);
}
strcpy(str,"HELLO");
shmdt(str);
exit(0);
}
else //parent read
{
wait(NULL);
str = shmat(shmid,NULL,0);
if(str == (void *) -1)
{
perror("shmat()");
exit(1);
}
puts(str);
shmdt(str);
shmctl(shmid,IPC_RMID,NULL);
exit(0);
}
exit(0);
}