- 引言--进程间通信
- 管道的概念
- 管道相关操作
- 有名管道及其相关操作
- 信号通信
一、引言--进程间通信
1)因为空间是独立和隔绝的,数据发不过去,需要进程间的通信来交互,所以需要通信。
2)linux进程间通信的常用几种方式:
1、古老的通信方式
无名管道 有名管道 信号2、IPC对象通信 system v BSD suse fedora kernel.org
消息队列(用的相对少,这里不讨论)
共享内存
信号量集3、socket通信
网络通信(不同主机间交互)
二、管道的概念
1) 无名管道 ===》pipe ==》只能给有亲缘关系进程通信
有名管道 ===》fifo ==》可以给任意单机进程通信
2)管道的特性:
1、管道是 半双工的工作模式
2、所有的管道都是特殊的文件不支持定位操作。不支持lseek->> fd fseek ->>FILE*
3、管道是特殊文件,读写使用文件IO。其中具有缓冲区,可以考虑,如果是字符串的话,使用fgets,fread,fgetc,
最好使用:open,read,write,close;
读写时机不同就会触发下面四个特性:
1)读端存在,一直向管道中去写,超过64k,读端来不及取出,写会阻塞。(阻塞是设备带来的特性,不由函数决定)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int main (int argc ,char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid > 0)
{
// fd[0] read fd[1] write
close (fd[0]); // close read
sleep(3); //shi qi du zu se ,shu jv jia gong xu yao shi jian
write(fd[1],"hello",6);
close(fd[1]);
}
else if( 0 == pid)
{
close (fd[1]);
char buf[10] = {0};
read (fd[0],buf,sizeof(buf));
printf("father:%s\n",buf);
close(fd[0]);
}
else
{
perror("fork");
return 1;
}
system("pause");
return 0;
}
2)写端是存在的,读管道,如果管道为空的话,读会阻塞。(读阻塞)
ps:即写的慢读的快。此时应该等一会。实际中系统读阻塞触发的次数居多,因为资源总数稀缺的。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
int main (int argc ,char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid > 0)
{
// fd[0] read fd[1] write
close (fd[0]); // close read
char buf[1024] = {0};
memset(buf,'a',sizeof(buf));
int i = 0;
for(i = 0 ; i < 65 ; i++)
{
write (fd[1],buf , 1024);
printf("%d\n", i);
}
close(fd[1]);
}
else if( 0 == pid)
{
close (fd[1]);
char buf[10] = {0};
while(1)
{
sleep(1);
}
close(fd[0]);
}
else
{
perror("fork");
return 1;
}
system("pause");
return 0;
}
3)读端的文件描述符关闭,读端关闭,只要调用写的相关函数,就会管道破裂(类似于段崩塌)导致写端结束,即进程结束。(可以用此特性来关闭进程)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
int main (int argc ,char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid > 0)
{
// fd[0] read fd[1] write
close(fd[0]); //close read end
sleep(3);
write(fd[1],"hello",5); //触发管道破裂 gdb 观察
printf("aaaa\n");
close(fd[1]);
}
else if( 0 == pid)
{
close (fd[1]);
close(fd[0]);
}
else
{
perror("fork");
return 1;
}
system("pause");
return 0;
}
触发管道破裂用gdb观察,gdb只能跟一个进程走,默认跟父走,若要跟子走,应该在fork之前输入下述命令:
set follow-fork-mode child
4)双方在通信过程中,写端先关闭,若果管道当中没有数据,那么将会读到0,read返回值为0,说明文件读到结尾。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
int main (int argc ,char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid > 0)
{
// fd[0] read fd[1] write
close (fd[0]); // close read
write(fd[1],"hello",5);
close(fd[1]);
exit(0);
}
else if( 0 == pid)
{
close (fd[1]);
sleep(3);
while(1)
{
char buf[10] = {0};
int ret = read(fd[0],buf,sizeof(buf));
if(0 == ret)
{
printf("read 0\n");
break;
}
printf("father :%s\n",buf);
}
close(fd[0]);
}
else
{
perror("fork");
return 1;
}
system("pause");
return 0;
}
三、无名管道相关操作
创建并打开管道-->读写管道-->关闭管道(看成特殊的文件操作)
无名管道 ===》管道的特例 ===>pipe函数
特性:
1)亲缘关系进程使用
2) 有固定的读写端
创建并打开管道:pipe函数
int pipe(int pipefd【2】)
参数:pipefd[0] ==>无名管道的固定读端
pipefd[1] ==>无名管道的固定写端
返回值:成功 0;失败 -1;
注:无名管道的创建应在fork之前完成。
读写管道:read();write();
与文件i/o的读写操作一致。
关闭管道:close()
代码示例:通过pipe实现图片复制
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (int argc ,char **argv)
{
int fd[2] = {0};
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid > 0) // readwenjian write guandao
{
// fd[0] read fd[1] write
close (fd[0]); // close write
int fd1 = open(argv[1],O_RDONLY);
if(-1 == fd1)
{
perror("open");
exit(1);
}
while(1)
{
char buf [58358] = {0};
int ret = read (fd1,buf,sizeof(buf));
if(0 == ret)
{
break;
}
write(fd[1],buf,ret);
}
close(fd[1]);
close(fd1);
}
else if( 0 == pid) //qu guan dao xie wen jian
{
close (fd[1]);
int fd2 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
if( -1 == fd2)
{
perror("child open");
return 1;
}
while(1)
{
char buf[58358] = {0};
int ret = read(fd[0],buf,sizeof(buf));
if(0 == ret)
{
break;
}
write(fd2,buf,ret);
}
close(fd[0]);
close(fd2);
}
else
{
perror("fork");
return 1;
}
// system("pause");
return 0;
}
相关问题:
1、父子进程是否都有fd[0] fd[1],如果在单一进程中写fd[1]能否直接从fd[0]中读到?
可以,写fd[1]可以从fd[0]读。
2、管道的数据存储方式是什么样的,数据是否一直保留?
栈, 先进后出
队列形式存储 读数据会剪切取走数据不会保留
先进先出
3、管道的数据容量是多少,有没有上限值。
操作系统的建议值: 512* 8 = 4k
代码测试实际值: 65536byte= 64k
4、管道的同步效果如何验证?读写同步验证。
读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止
写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞
结论:读写端必须同时存在,才能进行管道的读写。
5、固定的读写端是否就不能互换?能否写fd[0] 能否读fd[1]?
不可以,是固定读写端。
四、有名管道及其相关操作
本机上的任意进程的通信,向fifo中写,读从fifo中取。
创建有名管道-->打开有名管道-->读写管道-->关闭管道-->卸载有名管道(读端)
- 1)remove(文件名)为卸载命令,不删文件的话会一直存在,下次重新开机系统就会在里面开64k空间用于通信。
-
2)有名管道使用时当调用open时会阻塞,等对应的另一端出现再开始运行,只有打开有名管道时会出现;
-
创建有名管道:mkfifo函数(在指定的pathname路径+名称下创建一个权限为mode的有名管道文件)
-
int mkfifo(const char *pathname, mode_t mode);
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限。权限一般0666
返回值:成功 0, 失败 -1;打开有名管道 open
-
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数 -
注意:该函数使用的时候要注意打开方式, 因为管道是半双工模式,所有打开方式直接决定当前进程的读写方式。
-
有名管道的读写:文件i/o
-
读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));关闭管道:close();
-
卸载管道:remove();(将指定的pathname管道文件卸载,同时从文件系统中删除)
-
int unlink(const char *pathname);
参数: ptahtname 要卸载的有名管道
返回值:成功 0,失败 -1;#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> int main (int argc, char **argv) { int ret = mkfifo("fifo",0666); if( -1 == ret) { if(EEXIST != errno) { perror("mkfifo"); return 1; } } int fd = open("fifo",O_WRONLY); if( -1 == fd) { perror("open fifo"); return 1; } int srcfd = open(argv[1],O_RDONLY); if(-1 == srcfd) { perror("open"); exit(1); } while(1) { char buf [58358] = {0}; int ret = read (srcfd,buf,sizeof(buf)); if(0 == ret) { break; } write(fd,buf,ret); } // write(fd,"hello",5); close(fd); close(srcfd); // system("pause"); return 0; }
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> int main (int argc, char **argv) { int ret = mkfifo("fifo",0666); if( -1 == ret) { if(EEXIST != errno) { perror("mkfifo"); return 1; } } int fd = open("fifo",O_RDONLY); if( -1 == fd) { perror("open fifo"); return 1; } int dstfd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666); if( -1 == dstfd) { perror(" dstfd open"); return 1; } while(1) { char buf[58358] = {0}; int ret = read(fd,buf,sizeof(buf)); if(0 == ret) { break; } write( dstfd,buf,ret); } // char buf[10] = {0}; // read(fd,buf,sizeof(buf)); // printf("w:%s\n",buf); close(fd); close(dstfd); remove("fifo"); // system("pause"); return 0; }
有名管道 相关问题:
-
1、是否需要同步,以及同步的位置。
读端关闭 是否可以写,不能写什么原因。
写端关闭 是否可以读。结论:有名管道执行过程过必须有读写端同时存在。
如果有一端没有打开,则默认在open函数部分阻塞。2、有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。3、能否手工操作有名管道实现数据的传送。
读: cat fifoname
写: echo "asdfasdf" > fifoname
五、信号通信
应用:异步通信。 中断..
1~64;32应用编程。
//关闭
Term Default action is to terminate the process.
//忽略
Ign Default action is to ignore the signal.
wait
// 关闭,并保存关键点信息
Core Default action is to terminate the process and dump core (see
core(5)).
gdb a.out -c core
//暂停
Stop Default action is to stop the process.
//继续
Cont Default action is to continue the process if it is currently stopped.
1)信号kill -l ==>前32个有具体含义的信号
2)kill -xx xxxx
发送进程 信号 接收进程
kill -9 1000
a.out 9 1000 发送端:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig); //通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
sig 当前程序要发送的信号编号 《=== kill -l
返回值:成功 0; 失败 -1;
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
int main (int argc ,char **argv)
{
if(argc < 3)
{
printf("usage: ./a.out pid signum\n");
return 1;
}
pid_t pid = atoi(argv[1]);
int num = atoi(argv[2]);
int ret = kill(pid,num);
if(-1 == ret)
{
perror("kill");
return 1;
}
system("pause");
return 0;
}
3) (与kill相似)给进程自己发送sig信号
int raise(int sig)== kill(getpid(),int sig);
4)SIGALAM:定时由系统给当前进程发送信号,也称为闹钟函数。
闹钟只有一个,定时只有一次有效,但是必须根据代码逻辑是否执行判断。
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
int main ( int argc, char **argv)
{
alarm(5);
while(1)
{
printf("sleep...\n");
sleep(1);
}
system("pause");
return 0;
}
5)pause:进程暂停,不再继续执行,除非 收到其他信号。
int pause(void);
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
int main ( int argc, char **argv)
{
int i = 0;
while(1)
{
printf("work ...\n");
sleep(1);
i++;
if(5 == i)
{
pause();
}
}
return 0;
}
注意:接收端 每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、默认处理
2、忽略处理 9,19
3、自定义处理 9,19 捕获
6)信号注册函数signal原型:(回调函数)
void ( *signal(int signum, void (*handler)(int)) ) (int);
typedef void (*sighandler_t)(int);
===》void (*xx)(int); == void fun(int);
===》xx是 void fun(int) 类型函数的函数指针
===》typedef void(*xx)(int) sighandler_t; ///错误
typedef int myint;===>sighandler_t signal(int signum, sighandler_t handler);
===> signal(int sig, sighandler_t fun);
===> signal(int sig, xxx fun);
===>fun 有三个宏表示:SIG_DFL 表示默认处理
SIG_IGN 表示忽略处理
fun 表示自定义处理
示例1:
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
int flag = 0;
void handle(int num)
{
flag = 1;
}
int main ( int argc, char **argv)
{
signal(SIGALRM,handle);
alarm(5);
while(1)
{
if( 0 == flag)
{
printf("sleep...\n");
}
else
{
printf("working...\n");
}
sleep(1);
}
system("pause");
return 0;
}
示例2:
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
void handle(int num)
{
}
int main ( int argc, char **argv)
{
signal(SIGCONT,handle);
int i = 0;
while(1)
{
printf("work... pid:%d\n",getpid());
sleep(1);
i++;
if(5 == i )
{
pause();
}
}
system("pause");
return 0;
}
示例3:
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
void handle1(int num)
{
static int i = 0;
printf("老爸叫你...\n");
i++;
if(i ==3)
{
signal(SIGUSR1,SIG_IGN); //忽略信号
}
}
void handle2(int num)
{
static int i = 0;
printf("老妈叫你...\n");
i++;
if(i ==3)
{
signal(SIGUSR2,SIG_DFL); //回复默认信号
}
}
int main(int argc, char **argv)
{
signal(SIGUSR1,handle1);
signal(SIGUSR2,handle2);
while(1)
{
printf("i'm playing pid:%d\n",getpid());
sleep(1);
}
system("pause");
return 0;
}