传统的进程间通信(无名管道、有名管道、信号)
1.无名管道 (pipe)
查看命令: man 2 pipe
头文件:#include <unistd.h>
函数原型: int pipe(int pipefd[2]);
pipefd[2] :无名管道的两个文件描述符,int型的数组,大小为2,
pipefd[0]为读端,pipefd[1]为写端
返回值:
成功:返回0
失败:返回-1
2.无名管道的特点:
a、没有名字,因此无法使用open()打开
b、只能用于亲缘进程间(如父子进程、兄弟进程、祖孙进程等)通信
c、半双工工作方式,读写端是分开的,pipefd[0]为读端,pipefd[1]为写端
d、是一种特殊的文件,只存在内存中,由内核进行管理,随进程生,随进程死
e、对于它的读写可以使用文件IO如read、write函数
f、无名管道的操作属于一次性操作,如果对无名管道进行读操作,数据会被全部读走
单工:固定一种方向进行通信 广播
半双工:方向不定,但是同一时间只能由一端发送到另一端 对讲机
全双工:通信方向都可以,同时可以发送也可以接收 电话
一个进程用无名管道通信
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd[2];
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
return -1;
}
write(fd[1],"hello",5);
char buf[100]={0};
read(fd[0],buf,sizeof(buf));
printf("%s\n",buf);
close(fd[0]);
close(fd[1]);
return 0;
}
两个进程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
int fd[2];
pid_t pid;
int ret = pipe(fd);
if (ret < 0)
{
perror("pipe");
return -1;
}
pid = fork();
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid == 0)
{
close(fd[0]);
sleep(1);
int ret = write(fd[1],"hello",5);
if (ret < 0)
{
perror("write");
exit(-1);
}
close(fd[1]);
}
else if (pid > 0)
{
close(fd[1]);
char buf[100] = {0};
int ret = read(fd[0],buf,sizeof(buf));
if (ret < 0)
{
perror("read");
exit(-1);
}
printf("%s\n",buf);
close(fd[0]);
}
return 0;
}
3.管道读写注意事项:
1> 当管道中无数据时,执行读操作,读操作阻塞
2>向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
3>对无名管道的操作,类似一个队列,后写入的数据不会覆盖之前的数据,会在其后面存储,读取完的数据会从管道里面移除
4>向无名管道中写数据,将读端关闭,管道破裂,进程收到信号(SIGPIPE),默认这个信号会将进程杀死
4.有名管道 (fifo)
有名管道也叫命名管道,在本地磁盘可见的管道文件,可以进行文件IO操作。
特点:
1>不仅可在亲缘关系的进程间通信,也可以在不具有亲缘关系的进程间通信。
2>数据先进先出原则
3>支持除了 lseek之外的文件IO操作。
查看命令:man 3 mkfifo
函数原型:
int mkfifo(const char *pathname, mode_t mode);
参数:
*pathname:有名管道的名字-路径 例如:/home/farsight/myfifo
mode:八进制的权限, 例如:0777
在shell中使用mkfifo命令: mkfifo filename
eg: mkfifo f1
或者
if(mkfifo("f1",0666) == -1)
{
perror("mkfifo ");
return -1;
}
注意事项:
1>管道文件只有inode号,不占磁盘块空间,当使用open函数打开文件时才会在内核空间中创建管道。
2>创建有名管道mkfifo。跟普通文件一样使用,打开open,读read,写write,关闭close。
3>只有读端和写端同时存在管道才能打开成功。
5.有名管道和无名管道的异同点
1、相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
2、区别
有名在任意进程之间使用,无名在父子进程之间使用
有亲缘关系的进程用有名管道通信
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int ret = mkfifo("f1",0666);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid > 0)
{
int fd = open("f1",O_RDONLY);
char buf[100];
read(fd,buf,sizeof(buf));
printf("%s\n",buf);
close(fd);
}
else if (pid == 0)
{
int fd = open("f1",O_WRONLY);
write(fd,"hello",5);
close(fd);
}
return 0;
}
无亲缘关系的进程用有名管道通信
- 写端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
printf("等待读端.......\n");
int fd = open("f2",O_WRONLY);
if (fd < 0)
{
perror("open");
return -1;
}
while(1)
{
printf("input:");
char buf[100]= {0};
fgets(buf,sizeof(buf),stdin);
if (strncmp(buf,"quit",4) == 0)
{
break;
}
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
- 读端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
printf("等待写端........\n");
int fd = open("f2",O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
while(1)
{
char buf[100];
int ret = read(fd,buf,sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
if (ret == 0)
{
break;
}
if (strncmp(buf,"quit",4) == 0)
break;
printf("recv:%s",buf);
memset(buf,0,sizeof(buf));
}
return 0;
}
数据传输特点:
1、读端不存在时,写端写入数据将会阻塞
2、读端意外结束,写端再写数据将会管道破裂,该进程结束
6.信号
简单概念:信号是在软件层次上对中断机制的一种模拟
#include <signal.h>
int kill(pid_t pid, int signo); //kill把信号发送给进程或进程组;
int raise(int signo); //raise把信号发送给(进程)自身.
返回值:成功则返回0,
出错则返回-1
特点:
raise(signo); 等价于 kill(getpid(), signo);
alarm();//设置闹钟
pause();//程序暂停
可以为当前进程定义闹钟,时间到了会发出SIGALRM信号。
每个进程只能有一个alarm,当重新定义时,会重新计时。
如果之前定义了一个闹钟,则这次定义返回的是上次闹钟剩余的时间,否则返回0.
pause函数的作用,是让当前进程暂停运行,交出CPU给其他进程去执行;
当前进程进入pause状态后,当前进程会表现为“卡住、阻塞住”;
要退出pause状态,当前进程需要被信号唤醒。
信号的三种处理方式:
1.忽略 2.默认 3.自定义信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
参数:
/********************************
*signum: 捕获信号,设置信号的处理方式
*handler: SIG_IGN:忽略
* SIG_DFL:默认
* 自定义的信号处理函数
*********************************/
SIGINT : CTRL + C
SIGQUIT : CTRL + \
SIGTSTP : CTRL + Z
SIGKILL : 立即结束进程
SIGALRM : 当一个定时器结束时发出
SIGSTOP : 暂停一个进程
一、进程间通信方式
传统进程间通信方式:
无名管道、有名管道、信号
system V的IPC对象:
共享内存(share memory)、消息队列(message queue)、信号灯集
BSD: 套接字
二、共享内存share memor
是一种通信效率最高的进程间通信方式,
进程间通信时直接访问内存,不需要进行数据的拷贝。