在Linux系统中,有时候需要多个进程之间相互协作,共同完成某项任务,进程之间或线程之间有时候需要传递信息,有时候需要同步协调彼此工作,则就会出现进程间通信(interprocess communication 或者 IPC)
信号也是进程间通信的一种机制,尽管其主要作用不是这个,一个进程向另一个进程发送信号,传递的信息是信号编号,当采用sigqueue()函数发送信号时,还可以在信号上绑定数据(整型数字或指针),增强传递消息的能力,尽管如此,还是不建议将信号作为进程间通信的常规手段
//进程间的通信手段:
1、通信类:主要是在进程之间传递消息(消息队列),交换数据(通过共享一块内存来完成信息的交换)
2、同步类:协调进程间的操作,某些操作,多个进程间不能同时执行,需要借助一些同步类工具
匿名管道:
第一个广泛应用的进程间通信手段,日常在终端执行shall命令时,会大量使用管道,管道会被有血缘关系的进程共享,相当于创建了一个家庭公共场所(临界资源),每个家庭成员可以将信息(临界区)存放在里面,等待另一个成员来取走信息,阅后即焚,(读取管道的内容是消耗性的,读了之后就不会再管道中出现),任何时候家庭公共场所只能有一个成员去访问临界资源,访问期间支持原子性(对临界资源的操作成功或操作失败)
管道的实质:
内核维护了一块与管道文件相关联的缓冲区,对管道文件的操作,被内核转化成对这块缓冲区的操作
缺陷: 只能用于有血缘关系的进程之间,例如父子进程之间
#include
int pipe(pipefd[2]);
pipefd[2]//用来返回文件描述符的数组
pipefd[0]//为读打开,为读端
pipefd[1]//为写打开,为写端
//通常调用pipe()之后会调用fork进行创建父子进程,进行通信,对于管道数据的流向,要么父进程(写,关闭读端),子进程(读,关闭写端),要么子进程(写,关闭读端),父进程(读,关闭写端),在此之间,双方可以调用read(对于读进程)和write(对于写进程)对未关闭的文件描述符进行读写操作
//读写规则
1、读一个写端关闭的管道,在所有数据读完之后,read返回0,以指示文件到结尾处
2、如果写一个读端已关闭的管道,则产生SIGPIPE信号,捕捉信号write出错返回
3、互斥与原子性,在写的时候,读端不允许访问管道,并且已写尚未读取的字节数应该小于或等于PIPE_BUF(一般为4096字节)所规定的缓存大小
//当所有的读端与写端全部关闭后,管道才能被销毁
管道一般情况下单向通信,否则会出现内容混乱,可以建立两个管道支持双向通信
#include
#include
#include
#include
#include
int main()
{
int pipefd[2];//创建之后返回两个文件描述符pipefd[0]--读端 pipefd[2]--写端
pid_t cpid;
char buf;
if (pipe(pipefd) == -1)//创建管道
{
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();//创建子进程
if (cpid<0)
{
perror("fork cpid error");
exit(EXIT_FAILURE);
}
if (cpid == 0)//child
{
close(pipefd[0]);//关闭文件描述符0,读端
int i = 0;
char* _mesg = NULL;
while (i<20)
{
_mesg = "hello father ! i am child.\n ";
write(pipefd[1], _mesg, strlen(_mesg)+1);
sleep(1);
i++;
}
}
else//father
{
close(pipefd[1]);//父进程将argv[1]写入管道,关闭读端
char _mesg_f[100];
int j = 0;
while (j < 20)
{
memset(_mesg_f, '\0', sizeof(_mesg_f));
read(pipefd[0], _mesg_f, sizeof(_mesg_f));
sleep(3);
printf("%s\n", _mesg_f);
j++;
}
}
return 0;
}
管道内存区大小:
1、管道本质是一片内存区域,自然有大小,管道默认大小为65536字节(2的16次方),是内核内存中的一个缓冲区,可以用fcntl来获取和修改这个值得大小
pipe_capacity = fcntl(fd,?F_GETPIPE_SZ)
//获取管道大小
ret = fcntl(fd,?F_SETPIPE_SZ,size);
//设置管道大小
管道内存大小必须设置在页面大小和上限值之间
管道有大小,写入应该慎重,不能连续的写入大量数据,一旦管道填满,写入就会阻塞,对于读端,要及时读取数据,防止管道被写满,造成写入阻塞。
2、 pipe_buf .定义的是内核管道缓冲区的大小,这个值的大小是由内核设定的,这个值仅需一条命令就可以查到;而pipe capacity指的是管道的最大值,即容量,是内核内存中的一个缓冲区
命名管道FIFO:
为了解决无名管道而引入的,与匿名管道类似,最大的区别是,有实体文件与之关联,由于存在实体文件,不相关的没有血缘关系的进程也可以相互通信
#include
#include
int mkfifo(const char* pathname,mode_t mode)
pathname //要创建的或打开的文件名
mode // 存取访问限权
//命令创建:mkfifo [-m mode] pathname(创建命名管道的文件名)
// mknod [-m mode] pathname p(p表示要创建命名管道)
从外表看,我是一个FIFO 文件,有文件名,任何进程都可以打开
从核心看,与匿名管道一模一样,
if(mkfifo("/temp/fifo",S_IFIFO|0666) == -1)//尽量使用
{
perror("mkfifo error");
exit(1);
}
if(mknod("/temp/fifo",S_IFIFO|0666) == -1)
{
perror("mkfifo error");
exit(1);
}
//S_IFIFO | 0666 指明了创建一个命名管道的权限
用两个命名管道实现进程实现双向通信