一、背景
进程间相互独立,内部变量,别的进程不可见
由内核提供一份公共资源,让多个进程可以看见
条件
- 独立性
- 为了相互通信,有共享资源
- 共享由操作系统内核实现
二、匿名管道
前一个进程的标准输出作为后一个进程的标准输入
1.本质
内核提供的一段内存(队列),通过内存借助这段内存,完成进程间通信。然后将管道这段内存抽象成文件。通过访问文件描述符的形式,来读写这块内存中的数据。
2.特点
1.只适用于具有亲缘关系的进程
2.单向通信,半双工
3.面向字节流
4.内置同步互斥机制互斥:多个进程一起读,读到完整数据或者读不到数据
同步:管道为空,读阻塞;管道满了,写阻塞
5.生命周期随内存
所有引用这个管道的进程都销毁,管道才释放。真正释放的只是管道在内核中对应的这段内存,这个内存才是管道的本质
3.相关函数函数
pipe(创建一个匿名管道)
#include <unostd.h>
int pipe (int fd[2]);
//参数:fd:文件描述符数组;fd[0]:读端;fd[1]:写端
//返回值:创建成功返回0;创建失败返回错误码
eg:从输入读取数据,写入管道,读取管道,写到输出(默认阻塞式等待)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
int fd[2];
char buf[1024];
int len;
if (pipe(fd) == -1){
perror("make pipe");
return 1;
}
//read from stdin
while(fgets(buf , 1024 , stdin)) {
len = strlen(buf);
//write into pipe
if (write (fd[1] , buf , len) != len) {
perror("write tp pipe");
break;
}
memset (buf , 0x00 , sizeof(buf));
//read from pipe
if ((len = read(fd[0] , buf , 1024)) == -1){
perror("read from pipe");
break;
}
//write to stdout
if (write(1,buf,len) != len) {
perror("write to stdout");
break;
}
}
return 0;
}
三、命名管道 != 命名管道文件
1.本质
同匿名管道
内核提供的一段内存(队列),通过内存借助这段内存,完成进程间通信。然后将管道这段内存抽象成文件。通过访问文件描述符的形式,来读写这块内存中的数据。
2.特点
除第一条以外,其余与匿名管道相同
1.适用于任何进程
2.单向通信,半双工
3.面向字节流
4.内置同步互斥机制互斥:多个进程一起读,读到完整数据或者读不到数据
同步:管道为空,读阻塞;管道满了,写阻塞
5.生命周期随内存
所有引用这个管道的进程都销毁,管道才释放。真正释放的只是管道在内核中对应的这段内存,这个内存才是管道的本质
3.相关函数函数
同系统调用。
创建命名管道
//命名管道可以从命令行上创建,命令行方法是使用命令:
$ mkfifo filename
//创建管道文件,(p开头)
//命名管道也可以从程序里创建,相关函数有:
int mkfifo (const char* filename , mode_t mode);
//参数:(* filename)文件名;(mode)权限
int main() {
mkfifo("p2" , 0644);
return 0;
}
匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于创建与打开方式不同,除此之外,均一致
四、消息队列
1.本质
本质是内核中的一个链表,因此在访问消队列的时候,按照节点为单元,访问数据的读,写
2.特点
1.适用于任何进程
2.全双工,半通信
3.面向数据报按照节点为单位,一个一个读,一个一个写
4.内置同步互斥机制
5.生命周期随内核进程没了,消息队列还在,msgctl , IPC rm指令手动删除,或者重启(一直存在到显示删除/系统重启)
3.相关函数函数
//IPC(进程间通信)对象数据结构
//内核为每个IPC对象维护一个数据结构(消息队列,信号量,共享内存都有该结构)
//类型:业务上类型(用途)
struct ipc_perm {
//√
key_t __key; /* Key supplied to msgget(2) */
//IPCK,多个进程找到同一个消息队列
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
//√
unsigned short mode; /* Permissions */
//IPC对象权限
unsigned short __seq; /* Sequence number */
};
//消息队列结构
struct msqid_ds
{
//√
struct ipc_perm msg_perm; /* Ownership and permissions 各类IPC对象所共有的数据结构*/
//ipc_perm
//√
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
//链表
__kernel_time_t msg_stime; /* Time of last msgsnd(2) */
__kernel_time_t msg_rtime; /* Time of last msgrcv(2) */
__kernel_time_t msg_ctime; /* Time of last change */
unsigned long __msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long __msg_lqbytes; /* ditto */
unsigned short __msg_cbytes; /* Current number of bytes in queue (nonstandard) 消息队列中当前所保存的字节数 */
unsigned short __msg_qnum; /* Current number of messages in queue 消息队列中当前所保存的消息数 */
unsigned short __msg_qbytes; /* Maximum number of bytes allowed in queue 消息队列所允许的最大字节数 */
__kernel_ipc_pid_t msg_lspid; /* PID of last msgsnd(2) 最后一个发送数据的进程号*/
__kernel_ipc_pid_t msg_lrpid; /* PID of last msgrcv(2) 最后一个接受的进程号*/
};
注:ftok构造IPCK
消息队列函数
由于消息队列API,使用起来相对麻烦
1.我们把这些API封装起来
2.基于已经封装的代码,实现相应程序
$ ipcs -q ;
//查看有哪些消息队列
$ ipcrm -q mspid
//手动删除消息队列
[msgget]创建(用来访问和创建一个消息队列)
int msgget(key_t, key, int msgflg);
//参数:(key),消息队列名字;(msgflg),权限标志,表消息队列的访问权限,与文件的访问权限一样。IPC_CREAT,不存在就创建,存在就失败;IPC_EVCL,存在打开失败
//返回值:成功返回消息队列标识码;失败返回-1
[msgctl]销毁(控制函数)
int msgctl(int msgid, int cmd struct msgid_ds *buf);
//参数:(msgid),msgget函数返回的消息队列标识码;(cmd),将要采取的动作(有3个值);(*buf),指向msgid_ds的结构体指针,随cmd变化
//•IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
//•IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
//•IPC_RMID:删除消息队列
//返回值:成功返回0;失败返回-1
[msgsnd]添加
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
//参数:(msgid),由msgget函数返回的消息队列标识符;
// (*msgp),是一个指针,指向准备发送的消息;
// (msgsz),是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型;
// (msgflg),控制着当前消息队列满或者达到系统上限时将要发生的事情;msgflg = IPC_NOWAIT表示,消息队列满的时候不等待,直接返回EAGAIN错误
//返回值:成功返回0;失败返回-1
注:
1.消息队列在两方面受到制约
①它必须小于系统规定的上限值
②它必须以一个long int长整型开始,接受者函数将利用这个长整型确定消息的类型
2.消息结构参考形式:
struct msgbuf {
long mtype;//数据类型(业务上类型)
char mtext[1];
}
[msgrcv]接收
int msgrcv(int msgid, void *msgp, size_t msgsz, long msgtype, int msgflg);
//参数:(msgid),从哪个消息队列中读取,由msgget函数返回的消息队列标识码;
// (*msgp),指针,指向准备接收的消息;
// (msgsz),msgp指向消息的长度,不含保存消息类型的那个long int长整型;
// (msgtype),取哪一种类型,实现接收优先级的简单形式;
// (msgflg),控制着队列中没有相应类型的消息可供接收时将要发生的事。
//返回值:成功返回实际放到接收缓冲区里的字符个数,失败返回-1
注:
msgtype = 0;//返回队列第一条消息
msgtype > 0;//返回队列第一条类型等于msgtype的消息
msgtype < 0;//返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
msgflg = IPC_NOWAIT;//队列没有可读消息不等待,直接返回ENOMSG消息
msgflg = MSG_NOERROR;//消息大小超过msgsz时被截断
msgtype > 0 且 msgflg = MSG_EXCEPT;//接收类型不等于msgtype的第一条消息
五、共享内存
1.本质
同一块物理内存通过页表分别映射到不同进程的虚拟地址空间中,从而导致说,第一个内存修改变量,物理内存发生变化,第二个内存再去读取,就能感知到变量的改变
2.特点
1.适用于任何进程
只要构造相同IPCK,传相同PATHNAME以及相同PROJ_ID,自然可以得到相同的IPCK,打同一个共享内存(同消息队列)
2.双向通信(既能读,又能写)
3.没有同步互斥机制
4.不存在面向字节流和面向数据报的概念,只是一块内存,可以随意的读写数据,随机访问
5.生命周期随内核(没有手动销毁,一直存在到系统重启)
3.相关函数
[shmget]创建共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
//参数:(key),共享内存的标识符;(size),共享内存大小,PAGE_SIZE向上取整,4k的整数倍;(flag),权限
//返回值:成功返回共享内存标识符;失败返回-1
[shmat]"malloc"将共享内存段连接到进程地址空间
void* shmat(int shmid, const void *shmaddr, int shmflg);
//参数:(shmid),共享内存标识;(shmaddr),指定连接的地址;(shmflg),可能取SHM_RND或者SHM_RDONLY
//返回值:成功返回一个指向共享内存第一个节的指针;失败返回-1
[shmdt]"free"将共享内存段与当前进程脱离
int shmdt(char *shmaddr);
//参数:(shmaddr),由shmat返回的指针
//返回值:成功返回0;失败返回-1
//注:将共享内存段与当前进程脱离不等于删除共享内存段
[shmctl]"destroy"用于控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//参数:(shmid),由shmget返回的共享内存表示符;(cmd),将要采取的动作(三个可取值);
//•IPC_STAT:sh把mid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
//•IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
//•IPC_RMID:删除共享内存
//(buf),指向一个保存着共享内存的模式状态和访问权限的数据结构
//返回值:成功返回0;失败返回-1
4.优势
[共享内存效率极高]因为访问共享内存与普通内存没有任何差别,而要进行管道/消息队列的话,涉及到反复地把数据从内核拷贝到用户又从用户拷贝到内存,反复拷贝,开销积少成多
六、信号量
1.本质
信号量就是一个计数器。
信号量并不是让进程间能够直接的发送字符串数据,而是通过自身计数器的性质,来完成进程之间的同步和互斥。
二元信号量(类似于互斥锁)
计数器(可用资源剩余个数)+ 等待队列
并没有直接给进程间通信传递字符串信息,但是可以用于进程之间的同步与互斥
2.特点
1.适用于任何进程
2.生命周期随内核
3.同步与互斥
互斥
由于进程要共享资源,有些资源需要互斥使用,因此各进程间的竞争关系为互斥
系统中某些资源一次只允许一个进程使用,这样的资源称为临界资源或互斥资源
同步
为了完成一项任务,多个进程的先后顺序叫做同步
4.信号量,P,V
互斥:P、V 在同一个进程
同步:P、V 不在一个进程
P(申请资源) 信号量 -1
V(释放资源) 信号量 +1
信号量值含义:
S > 0:S表示可用资源个数
S = 0:表示无可用资源,无等待进程
S < 0:|S|表示等待队列中进程个数
注:system版本信号量,S 不可能 < 0
4.相关函数函数
[semget]创建/访问信号量
一个信号量数组,可以通过数组下标访问到具体
int semget(key_t key, int nsems, int semflg);
//参数:key,信号集的名字;nsems,信号集中信号量的个数;semflg,权限
//返回值:成功返回信号集标识码;失败返回-1
[semctl]控制信号量集(销毁)
int semctl(int semid, int semnum, int cmd, ...);
//参数:semid,信号量集标识符;semnum,信号量集数组上的下标,表示某一个信号量;cmd,将要采取的动作;最后一个参数根据命令不同而不同
//返回值:成功返回0;失败返回-1
IPC_STAT,从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中;
IPC_SET,设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值;
IPC_RMID,从内核中删除信号量集合(销毁);
SETVAL,用联合体中val成员的值设置信号量集合中单个信号量的值(初始化);
GETVAL,返回信号量集合内单个信号量的值
[semop]创建和访问一个信号量集
int semop(int semid, struct sembuf *sops, unsigned nsops);
//参数:semid,信号量集标识符 ; sops,指向进行操作的信号量集结构体数组的首地址 ; nsops,进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
//返回值:成功返回0;失败返回-1
struct sembuf {
short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/
short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
/*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
/*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
short flag; /*0 设置信号量的默认操作*/
/*IPC_NOWAIT设置信号量操作不等待*/
/*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
};
来源:CSDN
作者:giraffe_255
链接:https://blog.csdn.net/giraffe_255/article/details/80751198