信号是进程间通信的一种方式
1、为什么要进行通信?
数据传输:一个进程需要将它的数据发送给另一个进程。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2、
进程通信的分类
信号:软中断模拟机制,类似于通知
管道:可以进行数据传输,具有单向导通性以及阻塞
共享内存:多个进程共享一块数据,可以随时读取以及更改
信号量集:同步保护资源
消息队列:最符合通信思想,单纯收发数据
套接字:用于网络通信
3、常见的信号
信号2:SIGINT: ctrl +c
信号3:SIGQUIT:ctrl + \
信号9:SIGKILL
信号10、12:用户预留的信号
信号14:SIGALRM:闹钟信号,用于定时
信号19:SIGSTOP:暂停信号
信号20:SIGTSTP:暂停运行
信号18:SIGCONT 是继续进程
4、信号处理方法signal
函数原型:sighandler_t signal(int signum, sighandler_t handler);
函数功能:设置信号处理方式
函数参数:signum:要操作的信号,handler:对应信号处理方式,处理方式有以下几种
SIG_IGN //忽略处理
SIG_DFL //默认处理,即交给内核
函数名 //自定义处理
函数返回值:成功返回处理函数指针,失败返回SIG_ERR
函数头文件:#include <signal.h>
当我们运行程序时按下ctrl+c会阻断程序运行,实际上是给cpu传递了一个2信号。我们可以将2信号的处理方式改成在终端上打印一句《按了一下ctrl+c》。
编程
#include <stdio.h>
#include <signal.h>
void chuli(int ss);
int main()
{
signal(2,chuli);
while(1);
}
/*
更改的处理方法只能是typedef void (*sighandler_t)(int);格式
*/
void chuli(int ss)
{
printf("按了一下ctrl+c\n");
}
5、信号发送kill
函数原型:int kill(pid_t pid, int sig);
函数功能:向指定的进程发指定的信号
函数参数:Pid :指定的进程,pid>0 将信号发送给进程ID为pid的进程,pid==0将信号发送给同组进程,pid<0 将信号发送给其进程组ID等于pid绝对值的进程,pid==-1将信号发送给所有进程有以下几种
Sig:信号
函数返回值:成功返回0 失败返回-1
函数头文件:#include <sys/types.h>
#include <signal.h>
编程:给进程自己发送一个2信号
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *args[])
{
pid_t ss;
ss=getpid();
printf("发送2信号前\n");
kill(ss,2);
printf("发送2信号后\n");
}
6、向进程本身发送信号raise
函数原型:int raise(int sig);
函数功能:向进程本身发送信号
函数参数:Sig:信号类型
函数返回值:成功返回0 失败返回-1
函数头文件:#include <sys/types.h>
#include <signal.h>
编程:通过raise杀死进程本身
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc,char *args[])
{
printf("发送2信号前\n");
raise(2);
printf("发送2信号后\n");
}
7、闹钟函数alarm
函数原型:unsigned int alarm(unsigned int seconds);
函数功能:设置时间值,到达时间产生SIGALRM信号,不捕捉的时候默认终止进程(给进程本身发送)
函数参数:seconds:经过指定的时间后产生信号
函数返回值:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
函数头文件:#include<unistd.h>
编程:3s打印一次闹钟时间到
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include<unistd.h>
void alarm_func(int ss);
int main(int argc,char *args[])
{
signal(14,alarm_func);
alarm(3);
while(1);
}
void alarm_func(int ss)
{
printf("闹钟时间到\n");
alarm(3);
}
8、阻塞当前进程,直至捕捉一个信号pause
函数原型:int pause(void);
函数功能:是阻塞当前进程,直至捕捉到一个信号
函数参数:无
函数返回值:-1
函数头文件:#include<unistd.h>
编程:程序开始暂停运行,3s后继续运行
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include<unistd.h>
void alarm_func(int ss);
int main(int argc,char *args[])
{
signal(14,alarm_func);
alarm(3);
printf("运行pause函数,程序暂停\n");
pause();
printf("闹钟时间到,收到一个14信号,程序继续\n");
while(1);
}
void alarm_func(int ss)
{
printf("闹钟时间到,程序继续\n");
}
9、管道pipe
什么是管道?
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信
https://blog.csdn.net/JMW1407/article/details/107700451
一文了解管道
管道分为无名管道和有名管道
10、创建无名管道pipe
函数原型:int pipe(int filedes[2]);
函数功能:创建无名管道
函数参数:
filedes:接收打开管道文件的文件描述符
filedes[0]:存放管道文件的读端
filedes[1]:存放管道文件的写端
函数返回值:成功返回0,失败返回-1。
函数头文件:#include<unistd.h>
注意事项:
注意事项
必须在系统调用fork()之前调用pipe(),否则子进程将不会继承文件描述符
只有在管道的读端存在时向管道中写入数据才有意义
向管道中写入数据时,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。
父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用 sleep 函数。
编程:父进程发送hello!,子进程接收并打印
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include<unistd.h>
#include <string.h>
int main(int argc,char *args[])
{
int f[2];//f[0]read f[1]write
char str[20]="Hello!";
if(pipe(f)==0)
{
printf("创建无名管道成功%d\t%d\n",f[0],f[1]);
}
pid_t pid=fork();
if(pid>0)
{
//父进程
if(write(f[1],str,20)!=-1)
{
printf("父进程写入成功\n");
}
}else if(pid==0)
{
//子进程
sleep(1);//先让父进程写入,其实不要也行,因为如果子进程先读的话,文件缓冲区中没有内容,读会阻塞当前进程,直到缓冲区中出现内容
memset(str,0,20);
if(read(f[0],str,20)!=-1)
{
printf("子进程读取成功,读取的内容:%s\n",str);
}else
{
printf("子进程读取失败\n");
}
}
}
11、有名管道mkfifo
函数原型:int mkfifo(const char * pathname,mode_t mode)
函数功能:创建有名管道,创建成功后会生成一个管道文件,一个进程用open函数以O_RDONLY权限打开这个管道文件成为读端,另外一个用open函数以O_WRONLY权限打开这个管道文件成为写段,读端和写端必须都打开才能通信,否则会阻塞。
函数参数:pathname:要创建的FIFO文件的名字(带路径),mode:创建的FIFO文件的权限
函数返回值:成功返回0,失败返回-1。
函数头文件: #include <sys/stat.h>
#include<sys/types.h>
12、删除有名管道unlink
函数原型:int unlink(const char * pathname)
函数功能:删除文件
函数参数:pathname:要删除的FIFO文件的名字(带路径)
函数返回值:成功返回0,失败返回-1。
函数头文件: #include <unistd.h>
编程:实现两个进程的通信
写端:
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buff[20] = {"123"};
int ret = open("./111",O_WRONLY);
if(ret == -1)
{
perror("open");
return 0;
}
printf("打开管道成功\n正在写入\n");
//scanf("%s",buff);
while(1)
{
int res = write(ret,buff,20);
printf("写入了%d字节\n",res);
sleep(2);
}
//close(ret);
return 0;
}
读端:
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buff[20] = {0};
int ret = open("./111",O_RDONLY);
if(ret == -1)
{
perror("open");
return 0;
}
printf("打开管道成功\n正在读取\n");
read(ret,buff,20);
printf("buff:%s\n",buff);
close(ret);
return 0;
}
前两次读没有阻塞是因为写端开启了,第三次写端关闭读端运行时阻塞。