目录
一、IPC进程间通信
IPC 进程间通信 interprocess communicate
三大类
(1)古老的通信方式:无名管道 有名管道 信号;
(2)IPC对象通信 system v(unix操作系统) BSD suse fedora kernel.org
消息队列(用的相对少,这里不讨论)(与管道用法相似)
共享内存(给两个进程共享内存,相当于在内核中调malloc)
信号量集 (进程这里做互斥与同步用信号量)
(3)socket通信
网络通信(不同主机之间交互)
线程信号,posix sem_init
特例:古老的通信方式中信号是唯一的异步通信
所有的通信方式中共享内存是唯一的最高效
二、管道
1、管道分类:
无名管道、有名管道
2、用处:
无名管道 ===》pipe ==》只能给有亲缘关系进程通信
有名管道 ===》fifo ==》可以给任意单机进程通信
3、管道的特性:
(1)管道是 半双工的工作模式
(2)所有的管道都是特殊的文件不支持定位操作。不支持lseek->> fd fseek ->>FILE*
(3)管道是特殊文件,读写使用文件IO。其中具有缓冲区,可以考虑,如果是字符串的话,使用fgets,fread,fgetc,
最好使用:open,read,write,close;
三、管道的使用
1、使用关键
(1)读端存在,一直向管道中去写,超过64k,写会阻塞(一般取4k解除阻塞);
(2)写端是存在的,读管道,如果管道为空的话,读会阻塞;
(3)管道破裂(出现错误),读端关闭,写管道;
set follow-fork-mode parent
使用gdb调试时,敲上面的命令,进入子进程,一般默认父进程
(4) read 0 :写端关闭,如果管道没有内容,read 0 ;
2、使用步骤
创建管道 ==》读写管道 ==》关闭管道
四、无名管道pipe
无名管道 ===》管道的特例 ===>pipe函数
特性:
(1)亲缘关系进程使用
(2)有固定的读写端
1、 流程:
(1)创建并打开管道: pipe函数
头文件:#include <unistd.h>
函数原型:int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
pipefd[1] ==>无名管道的固定写端
返回值:成功 0
失败 -1;
注意事项:无名管道的架设应该在fork之前进行。
(2)无名管道的读写:文件IO的读写方式
读: read()
写: write()
(3)关闭管道: close()函数
2、代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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 end
sleep(3);
write(fd[1],"hello",5);
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;
}
3、练习:文件复制(无名管道)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.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)
{
// fd[0] read fd[1] write
close(fd[0]); //close read end
int srcfd = open("/home/linux/1.png",O_RDONLY);
if(-1 == srcfd)
{
perror("open");
exit(1);
}while(1)
{
char buf[4096]={0};
int ret = read(srcfd,buf,sizeof(buf));
if(0 == ret)
{
break;
}
write(fd[1],buf,ret); //sizeof(buf) strlen(buf)
}
close(fd[1]);
close(srcfd);
}else if (0 == pid)
{
close(fd[1]);
int dstfd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(-1 == dstfd)
{
perror(" child open");
return 1;
}
while(1)
{
char buf[4096]={0};
int ret = read(fd[0],buf,sizeof(buf));
if(0 == ret)
{
break;
}
write(dstfd,buf,ret);
}
close(fd[0]);
close(dstfd);}
else
{
perror("fork");
return 1;
}
// system("pause");
return 0;
}
4、补充问题
(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、使用步骤
创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道 ==》卸载有名管道
2、相关函数
(1)创建有名管道:mkfifo函数
头文件:#include <sys/types.h>
#include <sys/stat.h>
函数原型:int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限。
返回值:成功 0
失败 -1;
(2)打开有名管道 :open函数
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定
当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
可以以 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
(3)管道的读写: 文件IO
读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));
(4)关闭管道
close(fd);
(5)卸载管道:remove()函数
函数原型:int unlink(const char *pathname);
功能:将指定的pathname管道文件卸载,同时从文件系统中删除。
参数: ptahtname 要卸载的有名管道
返回值:成功 0
失败 -1;
3、代码示例
(1)fifo_w.c
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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;
}write(fd, "hello", 5);
close(fd);
// system("pause");
return 0;
}
(2)fifo_r.c
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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;
}
char buf[10] = {0};
read(fd, buf, sizeof(buf));
printf("w:%s\n", buf);
close(fd);
remove("fifo");
// system("pause");
return 0;
}
4、练习:文件复制(有名管道)
(1)fifo_w.c
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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("/home/linux/1.png",O_RDONLY);
if(-1 == srcfd)
{
perror("open 1.png");
return 1;
}
while(1)
{
char buf[4096]={0};
int ret = read(srcfd,buf,sizeof(buf));
if(0 == ret)
{
break;
}
write(fd,buf,ret);
}
close(fd);
close(srcfd);
// system("pause");
return 0;
}
(2)fifo_r.c
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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;
}
char buf[10] = {0};
read(fd, buf, sizeof(buf));
printf("w:%s\n", buf);
close(fd);
//remove("fifo");
// system("pause");
return 0;
}
5、补充问题
有名管道
(1)是否需要同步,以及同步的位置。
读端关闭 是否可以写,不能写什么原因。
写端关闭 是否可以读。结论:有名管道执行过程过必须有读写端同时存在。
如果有一端没有打开,则默认在open函数部分阻塞。(2)有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。(3)能否手工操作有名管道实现数据的传送。
读: cat fifoname
写: echo "asdfasdf" > fifoname
六、信号通信
应用:异步通信。 中断..
1~64;32应用编程。
1、如何响应:
收到信号默认采取以下动作:
//关闭(结束进程)
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.
2、信号的含义
信号 kill -l 命令 ==>前32个有具体含义的信号
3、发送端(发信号)
(1)命令
kill -xx xxxx
发送进程 信号 接收进程
kill -9 1000
a.out 9 1000
(2)kill函数(发送函数)
头文件:#include <sys/types.h>
#include <signal.h>
函数原型:int kill(pid_t pid, int sig);
功能:通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
sig 当前程序要发送的信号编号 《=== kill -l
返回值:成功 0
失败 -1;
(3)代码示例(kill)
#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>
// ./a.out 1000 9
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;
}
(4)raise函数(与kill相似)
int raise(int sig)== kill(getpid(),int sig);
功能:给进程自己发送sig信号
(5)alarm函数
函数原型:unsigned int alarm(unsigned int seconds);
发送的信号:SIGALAM
功能:定时由系统给当前进程发送信号,也称为闹钟函数
注:闹钟只有一个,定时只有一次有效,但是必须根据代码逻辑是否执行判断。
(6)代码示例(alarm)
#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;
}
(7)pause函数
int pause(void);
功能:进程暂停,不再继续执行,除非收到其他信号。
(8)代码示例(pause)
#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();
}
}
system("pause");
return 0;
}
4、接收端
每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、默认处理
2、忽略处理 9,19(9是强制关闭,19是强制暂停)
3、自定义处理 9,19 捕获
以上三种方式的处理需要在如下函数上实现。
(1)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 表示自定义处理
(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 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;
}
七、扩展
1、SIGCHLD
实际上,在子进程结束的时候,会产生一个SIGCHLD信号,信号描述如下,根据man手册可以知道,子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认处理动作是忽略。
SIGCHLD信号产生的条件主要有以下几个:
子进程终止时;
子进程接收到SIGSTOP信号停止时;
子进程处在停止态,接受到SIGCONT后唤醒时;