进程间通信
要想进行进程间通信,必须让相互通信的进程看到同一份资源。
匿名管道
匿名管道只能用于具有亲属关系间进程的通信,常用于父子。
匿名管道就是通过子进程是以父进程为模板创建的,因此子进程会也有和父进程相同的fd_arr(打开的文件列表),此时它们就看到了同一份资源。
管道是单向进行通信的。
管道创建
#include<unistd.h>
int pipe(int pipefd[2]);
//pipefd:文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端。
//返回值:成功返回0,出错时返回-1,并设置errno
如果我们的写端不关闭文件描述符,且不写入,读端可能需要长时间进行阻塞等待。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int pipefd[2];
pipe(pipefd);
pid_t id = fork();
if(id == 0)
{
//child
close(pipefd[0]);
char str[] = "I am child !!!";
while(1)
{
write(pipefd[1],str,strlen(str));
sleep(2);
}
}
else
{
close(pipefd[1]);
char buff[64]={0};
while(1)
{
ssize_t sz = read(pipefd[0],buff,63);
printf("%s\n",buff);
}
}
return 0;
}
当我们实际在进行写入时,写入条件不满足,写入端将被阻塞
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int pipefd[2];
pipe(pipefd);
pid_t id = fork();
if(id == 0)
{
//child
close(pipefd[0]);
char str[] = "I am child !!!";
int count = 1;
while(1)
{
write(pipefd[1],str,strlen(str));
//sleep(2);
printf("child :%d\n",count++);
}
}
else
{
close(pipefd[1]);
char buff[64]={0};
while(1)
{
ssize_t sz = read(pipefd[0],buff,63);
printf("%s\n",buff);
sleep(3);
}
}
return 0;
}
如果写端关闭文件描述符,读端将在读取完管道中的内容后读到文件结尾。
//当读端关闭时
int main()
{
int pipefd[2];
if(pipe(pipefd)== -1)
exit(1);
int pid = fork();
if(pid == 0){
//子进程向管道写入
close(pipefd[0]);
int count = 5;//当写入5条数据后关闭写端
while(count--)
{
write(pipefd[1],"a",1);
}
close(pipefd[1]);
cout<<"写端关闭"<<endl;
}
else{
close(pipefd[1]);
char ch;
while(1)
{
ssize_t sz = read(pipefd[0],&ch,1);
if(sz == 0){
cout<<"write close..."<<endl;
}
else
cout<<ch<<endl;
sleep(1);
}
}
return 0;
}
如果读端关闭,写端进程会收到13(SIGPIPE)号信号。
int main()
{
int pipefd[2];
if(pipe(pipefd) == -1)
exit(1);
int pid = fork();
if(pid == 0){
//child--->写入数据
close(pipefd[0]);
const char* str = "hello world";
while(true)
{
write(pipefd[1],str,strlen(str));
cout <<"写入数据..."<<endl;
sleep(1);
}
}
else{
close(pipefd[1]);//parent-->从管道读取数据
int count = 3;//只读取三次数据
char buff[64]="0";
while(count--)
{
ssize_t sz = read(pipefd[0],buff,sizeof(buff)-1);
buff[sz] = 0;
if(sz == 0){
//写段关闭
cout <<"child closr...."<<endl;
break;
}
cout << buff << endl;
}
close(pipefd[0]);
cout <<"读端退出"<<endl;
}
int state = 0;
waitpid(-1,&state,0);
//输出下子进程的退出码和是否收到信号
cout <<"退出码:"<< ((state >> 8)&0xff)<<endl;
cout <<"信号:"<<(state &0x7f)<<endl;
return 0;
}
特点
- 只能用于具有共同祖先的进程(就有亲缘关系的进程)之间进行通信;通常一个管道由一个进程创建,然后该进程创建子进程,此后,父子进程之间就可以使用该管道了。
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步和互斥。
- 管道是半双工的,数据只能向一个方向流动;需要双向通信时,需要建立两个管道。
- 管道是有大小的从Linux2.6.11开始,管道容量为65536字节。
- 规定小于PIGE_BUF字节的write必须是原子的,超过时写入可能是非原子的,PIGEZ_BUF至少为512字节,linux中为4096字节。
命名管道
创建命名管道
命令管道可以使任意两个进程进行通信。
int mkfifo(const char *pathname, mode_t mode);
//pathname创建的管道的名字
//mode:管道的权限。
也可以在命令行中创建
$ mkfifo filename
匿名管道和命令管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名函数由mkfifo函数创建,打开用open。
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。
管道通信本质都是内核中的一块缓冲区。
system V共享内存
共享内存区是最快的IPC形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存函数
ftok函数
将路径名和项目标识符转换为System V IPC的key值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:必须引用到现有的、可访问的文件。
proj_id:必须是非零的。
返回值:
成功时返回key值,失败时返回-1.
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字,通过ftok函数生成。
size:共享内存大小(一般为4k的整数倍)
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
IPC_CREAT:如果key标志空间存在则返回已创建的共享内存的标识符,否则创建新的共享内存。
IPC_EXCL:单独使用没有意义。
IPC_CREAT和IPC_EXCL一起使用可以保证shmget返回的标识符一定是最新的,否则就失败。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND(读写)和SHM_RDONLY(只读)
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数:
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作。
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
- 共享内存的声明周期随内核,所以必须显示的去释放。
- 在命令行上可以使用ipcs -m查看共享内存,使用 ipcrm -m shmid来进行删除内存,只有当共享内存的关联数为0时,才别删除掉。
- 共享内存不提供同步和互斥。