目录
进程通信介绍
进程通信方式
传统进程通信
system v IPC对象
注:本文只解释传统进程通信,system v IPC对象在下篇文章
无名管道
特点
注意事项
无名管道函数接口
代码实例
父进程循环从终端输入字符串,子进程循环打印数据
代码分析:
代码结果
有名管道
特点
注意事项
简单的有名管道的使用步骤(重点)
函数接口
错误处理方式:
代码实例
通过有名管道实现文件复制功能
读文件进程
写文件进程
信号
信号概念
常见的信号种类
信号的响应方式
信号的函数接口
signal(重点掌握)
代码实例
kill和raise
alarm:注意分清alarm和sleep区别,alarm只是到达时间后内核发送信号,alarm不是阻塞函数,不会和sleep一样阻塞等待时间,下面是结合pause函数的一段代码示例
signal(重点掌握)
进程通信是指两个或多个进程之间进行数据交换、共享资源或者互相协调的过程。进程通信的实现需要一定的机制和协议来保证通信的正确性、可靠性和效率。
无名管道:是一种半双工的通信方式,只能在具有父子或兄弟关系的进程之间使用。它使用pipe()函数创建管道,通过读写文件描述符来实现进程间通信。
有名管道:也叫FIFO(First In First Out),是一种可以在无亲缘关系的进程之间进行通信的机制。它使用mkfifo()函数创建管道,通过读写文件描述符来实现进程间通信。
信号:是一种异步的通信方式,用于通知目标进程发生了某种事件。它使用kill()函数发送信号,通过注册信号处理函数来处理信号。
共享内存:是一种允许不同进程访问同一块物理内存的方式。它可以通过映射一个共享内存区域到进程的虚拟地址空间来实现。不过由于多个进程可以同时访问同一块内存,因此需要加锁来保证数据的一致性。
消息队列:是一种实现进程间异步通信的方式。它使用msgget()函数创建消息队列,通过向消息队列发送消息实现进程间通信。
信号量:是一种用于实现进程间同步和互斥的机制。它使用semget()函数创建信号量集,通过P(wait)和V(signal)操作来控制对共享资源的访问。
int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端 fd[1]:写端
返回值:成功 0
失败 -1
#include
#include
#include
#include
#include
#define N 128
int main(int argc, char const *argv[])
{
char buf[N] = {};
int fd[2] = {};
pid_t pid;
if (pipe(fd) < 0)
{
perror("pipe err.");
return 0;
}
pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0)
{
while (1)
{
ssize_t len = read(fd[0], buf, N);
if (len < 0)
{
perror("read error");
return -1;
}
if (len == 0)
{ //读到文件结尾
printf("EOF\n");
return 0;
}
write(STDOUT_FILENO, buf, len); //写入标准输出
}
close(fd[0]); //关闭读取端
}
else
{
while (1)
{
if (fgets(buf, N, stdin) == NULL)
{
perror("fgets error");
return -1;
}
ssize_t len = write(fd[1], buf, strlen(buf) + 1);
if (len < 0)
{
perror("write error");
return -1;
}
}
close(fd[1]); //关闭写入端
wait(NULL); //等待子进程结束
}
return 0;
}
因为需要使用open函数来打开管道文件就牵扯到了下面特性:
int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
mode:权限
返回值:成功:0
失败:-1,并设置errno号
创建管道牵扯到若是管道文件已经存在,也会返回-1,此时普通的不等于0就是返回的判错方式无法达到我们想要的结果
在错误的同时会设置errno号,当errno==EEXIST的时候,说明文件存在,我们可以利用这个特性进行处理
#include
#include
#include
#include
#include
#include
#define N 128
int main(int argc, char const *argv[])
{
char buf[N] = "";
//1.创建有名管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
{
perror("fifo is cretae");
}
else
{
perror("mkfifo err.");
return -1;
}
}
//2.打开要读得文件
int fd;
fd = open("./1.txt", O_RDONLY);
if (fd < 0)
{
perror("open err.");
return -1;
}
//3.打开管道
int fifo_fd;
fifo_fd = open("./fifo", O_WRONLY);
if (fifo_fd < 0)
{
perror("open fifo err.");
return -1;
}
//4.循环读文件写到管道
while (1)
{
int num1 = read(fd, buf, N);
write(fifo_fd, buf, num1);
if (num1 == 0)
{
puts("hello");
break;
}
}
close(fd);
close(fifo_fd);
return 0;
}
#include
#include
#include
#include
#include
#include
#define N 128
int main(int argc, char const *argv[])
{
char buf[N] = "";
//1.创建有名管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
{
perror("fifo is cretae");
}
else
{
perror("mkfifo err.");
return -1;
}
}
//2.打开要写入得文件
int fd;
fd = open("./2.txt", O_WRONLY | O_CREAT | O_TRUNC ,0666);
if (fd < 0)
{
if (errno == EEXIST)
{
perror("2.txt create");
}
else
{
perror("open err.");
return -1;
}
}
//3.打开管道
int fifo_fd;
fifo_fd = open("./fifo", O_RDONLY);
if (fifo_fd < 0)
{
perror("open fifo err.");
return -1;
}
//4.循环读文件写到管道
while (1)
{
int num1 = read(fifo_fd, buf, N);
write(fd, buf, num1);
if (num1 == 0)
{
break;
}
}
close(fd);
close(fifo_fd);
return 0;
}
上面的fifo就是我们创建的管道文件,在属性中是p开头,且大小为0;有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中,过会通过代码就可体现出(详见特点第二条)
有名管道和无名管道区别统计
无名管道 |
有名管道 |
|
使用场景 |
具有亲缘关系的进程 |
不相干的进程 |
特点 |
半双工通信 固定的读端和写端 看做特殊的文件,通过文件IO操作 |
先进先出,不支持lseek 在文件系统中可见管道文件,数据存放在内存中 文件IO操作 |
函数 |
pipe 直接read、write |
mkfifo 先open、在进行read、write |
读写特性 |
当管道中无数据,都阻塞 当管道中写满数据,写阻塞 读端关闭,向管道中写数据管道破裂 |
只写方式,写阻塞,直到另一个进程将读打开 只读方式,读阻塞,直到另一个进程把写打开 可读可写,若管道中无数据,读阻塞 |
数值 | 名称 | 含义 |
2 |
SIGINT
|
结束进程,对应快捷方式ctrl+c
|
3 |
SIGQUIT
|
退出信号,对应快捷方式ctrl+\
|
9 |
SIGKILL
|
结束进程,不能被忽略不能被捕捉
|
15
|
SIGTERM
|
结束终端进程,kill 使用时不加数字默认是此信号
|
17 |
SIGCHLD
|
子进程状态改变时给父进程发的信号
|
19 |
SIGSTOP
|
结束进程,不能被忽略不能被捕捉
|
20 |
SIGTSTP
|
暂停信号,对应快捷方式ctrl+z
|
21 |
SIGALRM
|
闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程
|
上面表格中出现捕捉,忽略缺省等字样,他们是信号的响应方式
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
sig:要发送的信号
返回值:成功 0
失败 -1
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0
失败 -1
int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。
unsigned int alarm(unsigned int seconds)
功能:在进程中设置一个定时器
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则
返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时
已设置过闹钟时间,则之前的闹钟时间被新值所代替
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号
SIG_DFL:执行默认操作
handler:捕捉信号 void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
失败:-1
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
printf("测试语句1\n");
kill(getpid(),SIGINT);
//raise(SIGINT);
printf("测试语句2\n");
pause();
return 0;
}
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
alarm(5);
printf("测试语句1\n");
printf("测试语句2\n");
pause();//将进程挂起,直到收到信号为止。
return 0;
}
#include
#include
#include
#include
#include
void handly(int sig){
if(sig==SIGINT){
puts("1......SIGINT");
}
if(sig == SIGQUIT){
puts("2......SIGQUIT");
}
}
int main(int argc, char const *argv[])
{
signal(SIGINT,handly);//ctrl+c结束进程
signal(SIGQUIT,handly);//ctrl+\推出信号
pause();//捕捉到信号结束
return 0;
}
关于signal函数有道题目,详见http://t.csdn.cn/kNjJC