进程间通信
**管道**
**共享内存**
**消息队列**
**信号量**
1.为什么需要进程间通信?
原因是由于进程都拥有自己独立的进程虚拟地址空间,从而导致了进程的独立性;通过进程间通信,可以让不同的进程进行协作。
2.目前最大的进程间通信的技术是:网络!
管道
1.从命令来看
ps aux | grep zpf ==》 " | " 就是管道
ps 和 grep 是命令,同时要理解也是一个程序
ps aux 的输出结果, 通过管道,传输给了 grep 程序。换一句话说,ps aux 的结果作为grep程序的输入
2. 管道就是在内核当中开辟的一块内存,也可以称之为内核的缓冲区
1.匿名管道
1.1 在内核当中创建出来的一块内存是没有标识符的!
1.2 如何去创建一个管道
int pipe (int fd[2]);
fd[2] : 整型参数,有两个元素, fd[0], fd[1]
fd 是一个出参数
输出型参数:参数在函数内部赋值,在函数外部使用
输入型参数:参数在函数内部使用
输入输出型参数: 参数在函数内部使用,在函数外部使用
fd 数组当中保存两个文件描述符
fd[0] : 读端, 读匿名管道的内存
fd[1] : 写端, 往匿名管道当中进行写
返回值: 成功返回0, 失败返回-1
1.管道是单双工通信,数据流向只能是从写端,流向读端
2.管道提供流式服务,好处:读端可以决定每次读多少个字节
3.管道的生命周期跟随进程
int fcntl(int fd, int cmd, … /* arg */);
int fd : 当前要操作的文件描述符的数值
int cmd : 告诉fcntl 函数, 接下来你做什么事情
F_GETFL (void) :获取文件描述符的属性信息
F_SETFL (int) : 设置文件描述符的属性信息
返回值:
当时F_GETFL的时候,返回的时文件描述符的属性信息
4.匿名管道只支持具有亲缘关系的进程进行进程间通信
先创建管道,再fork子进程
1.管道到底能够存储多大的数据:
pipe_size : 64K
pipe_buf : 4K
进程操作写端往匿名管道当中写的时候,如果写入的字节数量小于4k,则保证当前写入操作的原子性:
2.管道对应得读写端文件描述符设置成为非阻塞属性:
文件描述符的非阻塞属性: O_NONBLOCK
int flag = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flag | O_NONBLOCK)
文件描述符的属性是按照位图的方式来使用的
1.将写端文件描述符设置成为非阻塞属性fd[1], 读端文件描述符不管
1.1 读端文件描述符都关闭,调用write进程会收到管道破裂的信号SIGPIPE,导致当前的调用write的进程退出掉
1.2 读端文件描述符不关闭,但是也不读,写端一直写,直到将管道写满,则在调用write函数,会返回-1,报错:资源不可用
2. 将读端文件描述符设置成为非阻塞属性fd[0], 写端文件描述符不管
2.1 写端文件描述符关闭,读端一直读,直到将管道的内容读完,再次调用read函数,返回0,表示没有读到任何内容
2.2 写端文件描述符不关闭,但是也不写,读端调用读,则read函数会返回-1, 表示资源不可用
2.命名管道
1.命名管道也是在内核当中开辟的一段缓冲区,这段缓冲区是有标识符的;意味着,不同的进程,不需要有亲缘关系,只要通过标识符就能找到缓冲区
2.命名管道的创建
2.1 命令创建
mkfifo[命名管道文件]
prw-rw-r-- 1 zpf zpf 0 Nov 1 09:33 fifl
“p” : 代表的文件类型为管道文件
2.2 函数创建
mkfifo函数
3.特性
1.命名管道的生命周期也是跟随进程的
2.命名管道是具有标识符的
3.其他的特性和匿名管道是一致的
共享内存
- 在物理内存当中开辟了一段空间
- 这段为物理内存空间,可以被不同的进程附加到自己的共享区当中
- 附加的进程,通过操作共享区,来交换数据
char* lp - (char*)malloc(10)
lp --> [地址]
strcpy(lp, “linux-57”)
共享内存的接口
1.创建共享内存
int shmget(key_t key, size_t size, int shmflg);
key : 共享内存标识符, 这个标识符相当于共享内存的身份证
程序员在第一次创建的时候,可以随意给值,只要和当前操作系统当中的其它的
共享内存标识符不重复
eg.0x99999999 0x88888888 0x12345678
size : 共享内存的大小,单位字节
shmflg:
IPC_CREAT : 如果共享内存不存在,则创建共享内存
IPC_EXCL : 需要搭配IPC_CREAT一起使用,这样的宏在搭配使用的时候,还是采
用按位或的方式(其实核心的思想就是位图)
eg : IPC_CREAT | IPC_EXCL :
如果想要获取的共享内存,已经存在,则报错
如果想要获取的共享内存,是刚刚创建出来的共享内存,则返回操作句柄
总结:使用shmget这个函数的时候一定要自己刚刚创建出来的共享内存
返回值:返回值是返回共享内存的操作句柄
共享内存的标识符和共享内存的操作句柄区别是什么?
标识符:是用来标识共享内存的,相当于共享内存的身份证,意味着不同的进程可以通过标识符找到这个共享内存
操作句柄:进程可以通过操作句柄来对共享内存进行操作(附加,分离,删除)
ipcs命令看到的内容:
结论1:共享内存的生命周期是跟随操作系统内核的
2.将共享内存附加到进程
void * shmat (int shmid, const void* shmaddr, int shmflg);
shmid:共享内存的操作句柄
shmaddr:附加到共享内存的生命虚拟地址,允许传递NULL值,让操作系统帮我们选择附加到共享区当中的哪那个地址,这个地址通过该函数的返回值返回给我们
shmflg:
SHM_RDONLY : 规定当前进程只能对共享存进行读操作
0 : 可读可写
返回值:返回共享内存附加到共享区的地址
结论2: 进程在读取共享内存的时候,是访问而不是拿走!
3.将共享内存和进程分离
int shmdt (const void* shmaddr);
shmaddr : shmat 的返回值
4.操作共享内存
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
shmid : 共享操作句柄
cmd : 告诉shmctl函数需要做什么操作
IPC_STAT : 获取当前共享内存的属性信息,放在buf当中,buf是出参
IPC_SET: 设置共享内存的属性信息,是用buf来进行设置的,buf是入参
IPC_RMID:删除共享内存,buf可以直接传递为NULL
buf:共享内存的结构体
5.删除共享内存
5.1 当使用shmctl或者使用ipcrm,删除共享内存之后,共享内存就实际被释放掉了
5.2 当共享内存被释放掉之后,共享内存的标识符会被设置成为0x00000000,表示其他进程不能通过之前的标识符找到该共享内存,并且共享内存的状态会被设置成为dest(destory)
5.2.1 当共享内存被释放掉之后,但是还是有进程在附加着共享内存,当前描述共享内
存的结构体并没有被释放,直到当前共享内存的附加进程数量为0的时候才会被释
放掉
//my_malloc()
if(lp)
{
count++;
return lp;
}
//malloc
count++;
return lp;
//my_free
if(count > 0)
{
count--;
}
if (count <= 0)
{
free();
}
消息队列
1.队列的特性是先进先出,消息对列也是满足先进先出的特性的,内核当中实现消息队列的时候,采用链表这个结构体
2.消息队列当中的元素是有类型的,每一种类型是有优先级概念的
同一类型保证先进先出特性
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);
msgflg:
IPC_NOWAIT : 非阻塞模式
0:阻塞模式
ssize_t magrcv(int maqid, void* msgp, size_t magsz, long msgtyp, int magflg);
long msgtype: 数据类型
== 0 : 取队列当中的第一个
> 0 : 取和msgtype相等的元素
< 0 : 先取绝对值,然后在范围内取最屌的优先级的元素
int msgctl(int msqid, int cmd, struct msqid_ds* buf);
结论:消息队列的生命周期也是跟随内核的
信号量
1.信号量的底层是一个计数器
2.型号量是用来进程控制的
临界资源:多个进程都能够访问到的资源,被称之为临界资源
多线程当中,多个线程都能访问到的资源被称之为临界资源
列举一下你所直到的进程间通信方式
管道,共享内存,消息队列,信号,网络,unix域套接字,信号量