<>一、背景

进程间相互独立,内部变量,别的进程不可见
由内核提供一份公共资源,让多个进程可以看见

条件

* 独立性
* 为了相互通信,有共享资源
* 共享由操作系统内核实现
<>二、匿名管道

前一个进程的标准输出作为后一个进程的标准输入
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记录自动恢复相应信号量的计数值*/ };

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信