通讯方式
- 管道:借助文件系统在多个进程之间建立一条数据通讯信道,各进程通过自己打开的文件描述符操作同一块内存空间,从而进行数据传递。管道的数据都是通过内存空间缓存的,属于半双工通讯,数据只能是单向传输。
- 特点:
面向字节流,
生命周期随内核
自带同步互斥机制。
半双工,单向通信,两个管道实现双向通信。
- 无名管道:pipe函数创建并打开的, 创建成功后,fds[0] 指向管道的读端 fds[1]指管道的写端,只能用于父子进程;
int pipe(int fds[2]);//返回值0表示成功,-1表示失败
fd[0]:读 fd[1]:写
子进程往管道中写数据,写数据时要关闭读端
父进程往管道中读数据,写数据时要关闭写端
写数据
int write(int fd, void *buff, size_t size);
读数据
int read(int fd, void *buff, size_t buff_len);
关闭
int close(int fd);
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2]; // 两个文件描述符
pid_t pid;
char buff[20];
if(pipe(fd) < 0) // 创建管道
printf("Create Pipe Error!\n");
if((pid = fork()) < 0) // 创建子进程
printf("Fork Error!\n");
else if(pid > 0) // 父进程
{
close(fd[0]); // 关闭读端
write(fd[1], "hello world\n", 12);
}
else
{
close(fd[1]); // 关闭写端
read(fd[0], buff, 20);
printf("%s", buff);
}
return 0;
}
- 有名管道:由mkfifo函数创建的 ,打开用int open(const char *pathname, int flag);读写函数和无名管道一样。
- 消息队列:消息一般是类型+数据,队列一般有优先级之分;进程可以根据消息类型独立的接收含有不同类型值的数据块。消息队列保存消息,它独立于发送和接收进程而存在,使得读端可以在写端离线的情况下工作,也不需要做同步控制。
消息队列也有管道⼀样的不⾜,就是每个消息的最⼤⻓度是有上限的(MSGMAX),每个消息队 列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有⼀个上限(MSGMNI)。
-
特点:
消息队列可以认为是一个全局的一个链表,链表节点钟存放着数据报的类型和内容,有消息队列的标识符进行标记。
消息队列允许一个或多个进程写入或者读取消息。
消息队列的生命周期随内核。
消息队列可实现双向通信。
创建或者获取
int msgget((key_t) key, int flag);
发送消息
int msgsnd(int msgid, const void *ptr, size_t nbytes, int flag);
ptr指向包含正的整形消息类型,在其后紧跟消息数据。则可定义以下结构
struct msgdata
{
long type;
char text[512];
};
接收消息
int msgrcv(int msgid, void *ptr, size_t nbytes, long type, int flag);
type: 指定要接收的消息的类型
type > 0 返回队列中消息类型为type的第一个消息
type == 0 返回队列中的第一个消息
type < 0 返回队列中消息类型小于或等于type绝对值的消息,如果有多个,则返回类型值最小的消息
销毁消息队列
int msgctl(int msgid, int cmd, struct msqid_ds *buf);
cmd: 当 cmd == IPC_RMID 时,消息消息队列
写端
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
#define MESSIZE 128
//消息的结构
struct my_message
{
long int message_type; //消息类型
char text[MESSIZE]; //消息内容
};
int main()
{
//创建一个消息队列
int msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
struct my_message message;
while(1)
{
//从消息队列中读取消息
if(msgrcv(msgid,(void*)&message,MESSIZE,2,0) == -1)
{
printf("msgrcv failed\n");
exit(1);
}
printf("you wrote:%s",message.text);
if(strncmp(message.text,"end",3) == 0)
{
break;
}
}
//删除消息队列
if(msgctl(msgid,IPC_RMID,NULL) == -1)
{
fprintf(stderr,"msgctl(IPC_RMID) failed\n");
exit(1);
}
}
读端
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
#define MESSIZE 128
//消息的结构
struct my_message
{
long int message_type; //消息类型
char text[MESSIZE]; //消息内容
};
int main()
{
//创建一个消息队列
int msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
struct my_message message;
while(1)
{
//从消息队列中读取消息
if(msgrcv(msgid,(void*)&message,MESSIZE,2,0) == -1)
{
printf("msgrcv failed\n");
exit(1);
}
printf("you wrote:%s",message.text);
if(strncmp(message.text,"end",3) == 0)
{
break;
}
}
//删除消息队列
if(msgctl(msgid,IPC_RMID,NULL) == -1)
{
fprintf(stderr,"msgctl(IPC_RMID) failed\n");
exit(1);
}
}
- 信号量:用于进程间同步控制,类似于一个计数器,当信号量的值大于0时,记录临界资源的个数,当信号量等于0时,进程访问临界资源时必须阻塞。对临界资源加以保护。
- 临界资源 -- 同一时刻只能被一个进程访问的资源
- 临界区 -- 访问临界资源的代码区域
- 原子操作 -- 不能被中断的操作,一旦开始,必须执行完成,中间不能被暂停
- P、V操作 -- P操作-1操作 V操作+1操作 互斥(进程竞争关系)不是同步(关注点在于探测临界资源方式)
- 进程同步: 进程的执行必须按照一定的顺序执行。事情必须一步一步完成。非阻塞情况下会不断探测是否可以进入就绪状态
- 进程异步: 进程的执行都是独立的,当一个异步调用发出后,调用者不能立刻得到结果时,进程不会阻塞,需要某些内核机制来通知其处理,自己不进行探测。
信号量值操作函数:
#include<sys/sem.h>
创建一个新信号量或取得一个已有信号量的键:
int semget(key_t key, int num_sems, int sem_flags); //返回值信号标识符sem_id。
key是整数值,不相关进程可以通过它访问同一个信号量
num_sems指定要创建/获取的信号量集中信号的个数,如果是获取已经存在的信号量,则将它设为0。
改变信号量的值:P V 操作由此函数完成
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops); //sem_ops是一个指向结构数组的指针,结构类型 如下:
struct sembuf{
short sem_num; //信号量编号,除非是一组信号量,否则一般为0表示这是第一个也是唯一的一个信号量
short sem_op; //信号量在一次操作中需要改变的数值:p(-1) /v(+1)
short sem_flg; //使操作系统跟踪当前进程对这个信号量的修改情况,通常被设为SEM_UNDO
}
控制信号量的信息:
int semctl(int sem_id,int sem_num,int command,…);
command是将要采取的动作。如果有第四个参数,它就是一个union semun结构:
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}
command常取的值为:
SETVAL:把信号量初始化为一个已知的值,由第四个参数中的val设置。
IPC_RMID:删除一个已经无需继续使用的信号量标识符
- 共享内存:通过内核对象,将多个进程中的一个虚拟地址映射到内核对象所申请的一块物理内存上,从而实现进程间数据通讯,还是以按页分配方式进行。
- 特点:共用一块内存,必须进程同步,互斥访问(信号量、互斥锁);借助虚拟地址,不需要数据拷贝(最快的进程间通讯方式)。
创建或者获取
int shmget(key_t key, size_t size, int flag);
连接到进程的虚拟地址空间
void * shmat(int shmid, const void *addr, int flag);
断开连接
int shmdt(void *ptr);
删除共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件(可读/写事件、I/O事件等)已经发生。
- 套接字(socket):套接字也是一种进程间的通信机制,与其他通信机制不同的是它一般用于网络间的进程通信。