Linux进程间通信的传统方式——管道和信号的介绍

发布于:2023-01-23 ⋅ 阅读:(301) ⋅ 点赞:(0)

进程间通信概念

进程间的通信即不同的进程之间交换数据的过程,包括数据传输,资源共享,通知事件,控制其他进程等。
Linux下的进程通信机制基本是从UNIX平台继承而来,
例如传统的通信方式包括无名管道、有名管道以及信号;
System Ⅴ进程间通信(IPC)包括System Ⅴ消息队列、System Ⅴ信号量、System Ⅴ共享内存等;
POSIX进程间通信机制包括POSIX消息队列、POSIX信号量、POSIX共享内存区。

本篇博客介绍传统的进程间通信方式,System Ⅴ的进程间通信方式在下一篇博客再介绍。

管道

从一个进程连接到另一个进程的一个数据流称为一个“管道“。管道分为无名管道和有名管道,区别在于创建的管道能否在文件系统中可见。

无名管道

​ 特点:
1. 创建之后在文件系统中不可见
2. 以半双工的方式进行通信
3. 拥有固定的读端和写端
4. 只能用于具有亲缘关系的进程间通信(如由fork得到的进程)

无名管道的创建:pipe函数
#include <unistd.h>
int pipe(int pipefd[2]);
参数:
pipefd :存放无名管道读端和写端的文件描述符数组
pipefd[0]——读端
pipefd[1]——写端
返回值:成功返回0,失败返回-1

注:无名管道的创建最好在fork创建子进程之前,不然子进程也会创建管道。

例子:在一个进程中创建一个子进程,子进程从键盘获取数据,父进程打印输出。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{ 
    int fd[2];
    if(pipe(fd)<0)
    {
        perror("pipe");
        return -1;
    }
    pid_t pid=fork();
    if(pid<0)
    {
        perror("fork");
        return -1;
    }
    if(pid==0)//子进程
    {
        char buf1[64]={0};
        while(1)
        {
            fgets(buf1,64,stdin);
            write(fd[1],buf1,strlen(buf1));
        }
    }
    else  //父进程
    {
        char buf2[64]={0};
        while(1)
        {
            memset(buf2,0,64);
            read(fd[0],buf2,64);
            fputs(buf2,stdout);
        }
    }

    return 0;
} 

无名管道的特性:

读特性:
	写端存在:
    	管道有数据:返回读到的字节数
    	管道无数据:阻塞
    写端不存在:
    	管道有数据:返回读到的字节数
    	管道无数据:返回0

写特性:
    读端存在:
        管道有空间:返回写入的字节数
    	管道无空间:阻塞,直到由空间为止
    读端不存在:
    	无论管道有无空间,管道破裂,系统内核会发送SIGPIPE信号给写进程并将其结束

有名管道

  • 有名管道创建之后会在文件系统中以管道文件的形式存在

  • 有名管道可用于任意两个进程间通信

有名管道的创建:mkfifo函数

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:pathname :文件名(可含路径)
    mode :文件权限(如0664)
返回值:成功返回0,失败返回-1

例子:创建一个有名管道,一个进程向管道中输入数据,另一个进程输出数据

//输入程序,里面有些头文件是我之前测试留的,不过对当前例子程序没啥用
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{ 
    int fd=open("fifo",O_WRONLY);
    if(fd<0)
    {
    	perror("open");
    	return -1;
	}else
	{
		printf("fifo succes\n");
	}
    char buf[64]={0};
    printf("输入\n");
    while(1)
    {
    	fgets(buf,64,stdin);
    	buf[strlen(buf)-1]='\0';
    	write(fd,buf,strlen(buf));
    }
    return 0;
} 
//输出程序,里面有些头文件是我之前测试留的,不过对当前例子程序没啥用
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{ 
        int ret=mkfifo("fifo",0664);    
		if(ret<0)
		{
			perror("mkfifo");
			return -1;
		}
    	int fd=open("fifo",O_RDONLY);
    	if(fd<0)
    	{
    		perror("open");
    		return -1;
		}else
		{
			printf("fifo succes\n");
		}
    	char buf1[64]={0};
    	while(1)
    	{
    		memset(buf1,0,64);
    		read(fd,buf1,64);
    		printf("输出%s\n",buf1);
    	}
    
    return 0;
} 

信号

信号:是中断机制在软件层次上的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求是基本类似的。
信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。

信号的处理方式有三种:
​ 默认处理,Linux对每种信号都规定了默认操作
​ 忽略,即对信号不做任何处理。不过有两个特殊信号不能忽略,SIGKILL、SIGSTOP
​ 捕获信号,定义信号处理函数,当信号发生时,执行相应的处理函数

  • 如果一个进程当前并未处于执行态,则发送给该进程的信号由内核保存起来,直到该进程恢复执行再传递给它
  • 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号相关指令

kill -l //可查看信号类型
可得如下图的信号编号以及相应宏
在这里插入图片描述

kill -信号编号 进程号PID
可向指定进程发送对应编号的信号,信号编号也可以是宏,因为宏的值就是数字编号
例:
kill -9 11023 //向进程11023发送信号9
kill -9 -11023 //向进程组11023发送信号9
kill -9 -1 //向除了init进程以外的其他所有进程发送信号9

信号相关函数

1)kill()

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid :指定进程号
sig : 信号编号或者其对应宏
返回值:成功返回0,失败返回-1

2)raise()

#include <signal.h>
int raise(int sig);
参数:sig : 信号编号或者其对应宏
返回值:成功返回0,失败返回-1
功能:向当前进程或线程发送信号

3)定时器 alarm()

  • 一个进程中最多只能存在一个定时器

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:
seconds :定时秒数
返回值:
成功则返回上一次定时器定时剩余时间,第一次定时的话返回0

#include <unistd.h>
int pause(void);
功能:该进程(或线程)休眠(阻塞),直到发出终止进程或调用信号捕获函数的信号
返回值:仅在捕获到信号并返回信号捕获函数时返回。 在这种情况下,pause() 返回 -1,并且 errno 设置为 EINTR

4)捕获信号

#include <signal.h>
typedef void (*sighandler_t)(int);//信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum :指定信号
handler :信号处理函数,这个处理函数有个固定的参数,为捕获的信号的编号
signal() 将信号符号的处置设置为处理程序,它可以是 SIG_IGN、SIG_DFL 或程序员定义的函数(“信号处理程序”)的地址
SIG_IGN :选择以忽略方式处理指定信号
SIG_DFL :选择以默认方式处理指定信号
返回值:返回信号处理程序的前一个值,如果出错则返回 SIG_ERR。