Linux系统编程学习--第6天信号以及管道

信号是进程间通信的一种方式

1、为什么要进行通信?

数据传输:一个进程需要将它的数据发送给另一个进程。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2、

进程通信的分类
	信号:软中断模拟机制,类似于通知
	管道:可以进行数据传输,具有单向导通性以及阻塞
	共享内存:多个进程共享一块数据,可以随时读取以及更改
	信号量集:同步保护资源
	消息队列:最符合通信思想,单纯收发数据
	套接字:用于网络通信

3、常见的信号

	信号2:SIGINT: ctrl +c
	信号3:SIGQUIT:ctrl + \
	信号9:SIGKILL
	信号1012:用户预留的信号
	信号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;
}

在这里插入图片描述
前两次读没有阻塞是因为写端开启了,第三次写端关闭读端运行时阻塞。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值